├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── client ├── .editorconfig ├── .env.development ├── .env.production ├── .env.staging ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── Dockerfile ├── Dockerfile_dev ├── LICENSE ├── README-zh.md ├── README.md ├── babel.config.js ├── build │ └── index.js ├── jest.config.js ├── jsconfig.json ├── mock │ ├── index.js │ ├── mock-server.js │ ├── table.js │ └── user.js ├── package.json ├── postcss.config.js ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.vue │ ├── api │ │ ├── dict.js │ │ ├── file.js │ │ ├── moritor.js │ │ ├── org.js │ │ ├── perm.js │ │ ├── position.js │ │ ├── role.js │ │ ├── table.js │ │ ├── task.js │ │ ├── user.js │ │ └── workflow.js │ ├── assets │ │ └── 404_images │ │ │ ├── 404.png │ │ │ └── 404_cloud.png │ ├── components │ │ ├── Breadcrumb │ │ │ └── index.vue │ │ ├── Hamburger │ │ │ └── index.vue │ │ ├── Pagination │ │ │ └── index.vue │ │ └── SvgIcon │ │ │ └── index.vue │ ├── directive │ │ └── el-table │ │ │ ├── adaptive.js │ │ │ └── index.js │ ├── icons │ │ ├── index.js │ │ ├── svg │ │ │ ├── 404.svg │ │ │ ├── bug.svg │ │ │ ├── chart.svg │ │ │ ├── clipboard.svg │ │ │ ├── component.svg │ │ │ ├── dashboard.svg │ │ │ ├── documentation.svg │ │ │ ├── drag.svg │ │ │ ├── edit.svg │ │ │ ├── education.svg │ │ │ ├── email.svg │ │ │ ├── example.svg │ │ │ ├── excel.svg │ │ │ ├── exit-fullscreen.svg │ │ │ ├── eye-open.svg │ │ │ ├── eye.svg │ │ │ ├── form.svg │ │ │ ├── fullscreen.svg │ │ │ ├── guide.svg │ │ │ ├── icon.svg │ │ │ ├── international.svg │ │ │ ├── language.svg │ │ │ ├── link.svg │ │ │ ├── list.svg │ │ │ ├── lock.svg │ │ │ ├── message.svg │ │ │ ├── money.svg │ │ │ ├── nested.svg │ │ │ ├── password.svg │ │ │ ├── pdf.svg │ │ │ ├── people.svg │ │ │ ├── peoples.svg │ │ │ ├── position.svg │ │ │ ├── qq.svg │ │ │ ├── search.svg │ │ │ ├── shopping.svg │ │ │ ├── size.svg │ │ │ ├── skill.svg │ │ │ ├── star.svg │ │ │ ├── tab.svg │ │ │ ├── table.svg │ │ │ ├── theme.svg │ │ │ ├── tree-table.svg │ │ │ ├── tree.svg │ │ │ ├── user.svg │ │ │ ├── wechat.svg │ │ │ └── zip.svg │ │ └── svgo.yml │ ├── layout │ │ ├── components │ │ │ ├── AppMain.vue │ │ │ ├── Navbar.vue │ │ │ ├── Sidebar │ │ │ │ ├── FixiOSBug.js │ │ │ │ ├── Item.vue │ │ │ │ ├── Link.vue │ │ │ │ ├── Logo.vue │ │ │ │ ├── SidebarItem.vue │ │ │ │ └── index.vue │ │ │ └── index.js │ │ ├── index.vue │ │ └── mixin │ │ │ └── ResizeHandler.js │ ├── main.js │ ├── permission.js │ ├── router │ │ └── index.js │ ├── settings.js │ ├── store │ │ ├── getters.js │ │ ├── index.js │ │ └── modules │ │ │ ├── app.js │ │ │ ├── permission.js │ │ │ ├── settings.js │ │ │ └── user.js │ ├── styles │ │ ├── element-ui.scss │ │ ├── index.scss │ │ ├── mixin.scss │ │ ├── sidebar.scss │ │ ├── transition.scss │ │ └── variables.scss │ ├── utils │ │ ├── auth.js │ │ ├── get-page-title.js │ │ ├── index.js │ │ ├── permission.js │ │ ├── request.js │ │ ├── scroll-to.js │ │ └── validate.js │ ├── vendor │ │ ├── Export2Excel.js │ │ └── Export2Zip.js │ └── views │ │ ├── 404.vue │ │ ├── dashboard │ │ └── index.vue │ │ ├── form │ │ └── index.vue │ │ ├── login │ │ └── index.vue │ │ ├── monitor │ │ └── service.vue │ │ ├── nested │ │ ├── menu1 │ │ │ ├── index.vue │ │ │ ├── menu1-1 │ │ │ │ └── index.vue │ │ │ ├── menu1-2 │ │ │ │ ├── index.vue │ │ │ │ ├── menu1-2-1 │ │ │ │ │ └── index.vue │ │ │ │ └── menu1-2-2 │ │ │ │ │ └── index.vue │ │ │ └── menu1-3 │ │ │ │ └── index.vue │ │ └── menu2 │ │ │ └── index.vue │ │ ├── system │ │ ├── changepassword.vue │ │ ├── dict.vue │ │ ├── file.vue │ │ ├── organization.vue │ │ ├── perm.vue │ │ ├── position.vue │ │ ├── role.vue │ │ ├── task.vue │ │ └── user.vue │ │ ├── table │ │ └── index.vue │ │ ├── tree │ │ └── index.vue │ │ └── workflow │ │ ├── configuration.vue │ │ ├── customfield.vue │ │ ├── index.vue │ │ ├── state.vue │ │ ├── test.vue │ │ ├── ticket.vue │ │ ├── ticketDetail.vue │ │ ├── ticketHandle.vue │ │ ├── transitions.vue │ │ └── workFlowTickets.vue ├── tests │ └── unit │ │ ├── .eslintrc.js │ │ ├── components │ │ ├── Breadcrumb.spec.js │ │ ├── Hamburger.spec.js │ │ └── SvgIcon.spec.js │ │ └── utils │ │ ├── formatTime.spec.js │ │ ├── parseTime.spec.js │ │ └── validate.spec.js └── vue.config.js ├── client_mp ├── .gitignore ├── App.vue ├── LICENSE ├── README.md ├── common │ ├── classify.data.js │ ├── demo.scss │ ├── http.api.js │ ├── http.interceptor.js │ ├── index.list.js │ ├── locales │ │ ├── en.js │ │ └── zh.js │ └── vue-i18n.min.js ├── components │ └── page-nav │ │ └── page-nav.vue ├── main.js ├── manifest.json ├── package.json ├── pages.json ├── pages │ ├── componentsA │ │ ├── avatar │ │ │ └── index.vue │ │ ├── avatarCropper │ │ │ └── index.vue │ │ ├── backTop │ │ │ └── index.vue │ │ ├── calendar │ │ │ └── index.vue │ │ ├── empty │ │ │ └── index.vue │ │ ├── field │ │ │ └── index.vue │ │ ├── form │ │ │ └── index.vue │ │ ├── fullScreen │ │ │ └── index.vue │ │ ├── icon │ │ │ └── index.vue │ │ ├── indexList │ │ │ └── index.vue │ │ ├── keyboard │ │ │ └── index.vue │ │ ├── lazyLoad │ │ │ └── index.vue │ │ ├── modal │ │ │ └── index.vue │ │ ├── navbar │ │ │ └── index.vue │ │ ├── noNetwork │ │ │ └── index.vue │ │ ├── parse │ │ │ └── index.vue │ │ ├── select │ │ │ └── index.vue │ │ ├── slider │ │ │ └── index.vue │ │ ├── tabs │ │ │ └── index.vue │ │ ├── tag │ │ │ └── index.vue │ │ ├── test │ │ │ └── index.vue │ │ ├── timeLine │ │ │ └── index.vue │ │ ├── toast │ │ │ └── index.vue │ │ ├── topTips │ │ │ └── index.vue │ │ └── verificationCode │ │ │ └── index.vue │ ├── componentsB │ │ ├── card │ │ │ └── index.vue │ │ ├── checkbox │ │ │ └── index.vue │ │ ├── divider │ │ │ └── index.vue │ │ ├── dropdown │ │ │ └── index.vue │ │ ├── image │ │ │ └── index.vue │ │ ├── line │ │ │ └── index.vue │ │ ├── loading │ │ │ └── index.vue │ │ ├── noticeBar │ │ │ └── index.vue │ │ ├── picker │ │ │ └── index.vue │ │ ├── radio │ │ │ └── index.vue │ │ ├── rate │ │ │ └── index.vue │ │ ├── readMore │ │ │ └── index.vue │ │ ├── search │ │ │ └── index.vue │ │ ├── skeleton │ │ │ └── index.vue │ │ ├── steps │ │ │ └── index.vue │ │ ├── sticky │ │ │ └── index.vue │ │ ├── swipeAction │ │ │ └── index.vue │ │ ├── swiper │ │ │ └── index.vue │ │ ├── switch │ │ │ └── index.vue │ │ ├── tabbar │ │ │ └── index.vue │ │ ├── table │ │ │ └── index.vue │ │ ├── upload │ │ │ └── index.vue │ │ └── waterfall │ │ │ └── index.vue │ ├── componentsC │ │ ├── actionSheet │ │ │ └── index.vue │ │ ├── alertTips │ │ │ └── index.vue │ │ ├── badge │ │ │ └── index.vue │ │ ├── button │ │ │ └── index.vue │ │ ├── cell │ │ │ └── index.vue │ │ ├── circleProgress │ │ │ └── index.vue │ │ ├── collapse │ │ │ └── index.vue │ │ ├── color │ │ │ └── index.vue │ │ ├── countDown │ │ │ └── index.vue │ │ ├── countTo │ │ │ └── index.vue │ │ ├── gap │ │ │ └── index.vue │ │ ├── grid │ │ │ └── index.vue │ │ ├── layout │ │ │ └── index.vue │ │ ├── link │ │ │ └── index.vue │ │ ├── loadmore │ │ │ └── index.vue │ │ ├── mask │ │ │ └── index.vue │ │ ├── messageInput │ │ │ └── index.vue │ │ ├── numberBox │ │ │ └── index.vue │ │ ├── popup │ │ │ └── index.vue │ │ ├── progress │ │ │ └── index.vue │ │ ├── section │ │ │ └── index.vue │ │ ├── subsection │ │ │ └── index.vue │ │ └── test │ │ │ └── index.vue │ ├── example │ │ ├── components.config.js │ │ ├── components.vue │ │ ├── js.config.js │ │ ├── js.vue │ │ ├── template.config.js │ │ └── template.vue │ ├── home │ │ └── home.vue │ ├── library │ │ ├── color │ │ │ └── index.vue │ │ ├── colorSwitch │ │ │ └── index.vue │ │ ├── debounce │ │ │ └── index.vue │ │ ├── deepClone │ │ │ └── index.vue │ │ ├── deepMerge │ │ │ └── index.vue │ │ ├── getRect │ │ │ └── index.vue │ │ ├── globalVariable │ │ │ ├── globalData.vue │ │ │ ├── index.vue │ │ │ ├── prototype.vue │ │ │ └── vuex.vue │ │ ├── guid │ │ │ └── index.vue │ │ ├── http │ │ │ └── index.vue │ │ ├── md5 │ │ │ └── index.vue │ │ ├── mpShare │ │ │ └── index.vue │ │ ├── queryParams │ │ │ └── index.vue │ │ ├── random │ │ │ └── index.vue │ │ ├── randomArray │ │ │ └── index.vue │ │ ├── route │ │ │ ├── index.vue │ │ │ └── routeTo.vue │ │ ├── test │ │ │ └── index.vue │ │ ├── timeFormat │ │ │ └── index.vue │ │ ├── timeFrom │ │ │ └── index.vue │ │ └── trim │ │ │ └── index.vue │ ├── login │ │ ├── code.vue │ │ └── login.vue │ ├── my │ │ └── my.vue │ ├── template │ │ ├── address │ │ │ ├── addSite.vue │ │ │ └── index.vue │ │ ├── citySelect │ │ │ ├── index.vue │ │ │ └── u-city-select.vue │ │ ├── comment │ │ │ ├── index.vue │ │ │ └── reply.vue │ │ ├── coupon │ │ │ └── index.vue │ │ ├── douyin │ │ │ └── index.nvue │ │ ├── keyboardPay │ │ │ └── index.vue │ │ ├── login │ │ │ ├── code.vue │ │ │ └── index.vue │ │ ├── mallMenu │ │ │ ├── index1.vue │ │ │ └── index2.vue │ │ ├── order │ │ │ └── index.vue │ │ ├── submitBar │ │ │ └── index.vue │ │ └── wxCenter │ │ │ └── index.vue │ └── uview │ │ ├── uview.config.js │ │ └── uview.vue ├── static │ ├── common │ │ └── js │ │ │ └── touch-emulator.js │ └── uview │ │ ├── common │ │ ├── favicon.ico │ │ └── logo.png │ │ └── example │ │ ├── component.png │ │ ├── component_select.png │ │ ├── js.png │ │ ├── js_bak.png │ │ ├── js_select.png │ │ ├── js_select_bak.png │ │ ├── min_button.png │ │ ├── min_button_select.png │ │ ├── template.png │ │ └── template_select.png ├── store │ ├── $u.mixin.js │ └── index.js ├── template.h5.html ├── uni.scss ├── uview-ui │ ├── LICENSE │ ├── README.md │ ├── components │ │ ├── u-action-sheet │ │ │ └── u-action-sheet.vue │ │ ├── u-alert-tips │ │ │ └── u-alert-tips.vue │ │ ├── u-avatar-cropper │ │ │ ├── u-avatar-cropper.vue │ │ │ └── weCropper.js │ │ ├── u-avatar │ │ │ └── u-avatar.vue │ │ ├── u-back-top │ │ │ └── u-back-top.vue │ │ ├── u-badge │ │ │ └── u-badge.vue │ │ ├── u-button │ │ │ └── u-button.vue │ │ ├── u-calendar │ │ │ └── u-calendar.vue │ │ ├── u-car-keyboard │ │ │ └── u-car-keyboard.vue │ │ ├── u-card │ │ │ └── u-card.vue │ │ ├── u-cell-group │ │ │ └── u-cell-group.vue │ │ ├── u-cell-item │ │ │ └── u-cell-item.vue │ │ ├── u-checkbox-group │ │ │ └── u-checkbox-group.vue │ │ ├── u-checkbox │ │ │ └── u-checkbox.vue │ │ ├── u-circle-progress │ │ │ └── u-circle-progress.vue │ │ ├── u-col │ │ │ └── u-col.vue │ │ ├── u-collapse-item │ │ │ └── u-collapse-item.vue │ │ ├── u-collapse │ │ │ └── u-collapse.vue │ │ ├── u-column-notice │ │ │ └── u-column-notice.vue │ │ ├── u-count-down │ │ │ └── u-count-down.vue │ │ ├── u-count-to │ │ │ └── u-count-to.vue │ │ ├── u-divider │ │ │ └── u-divider.vue │ │ ├── u-dropdown-item │ │ │ └── u-dropdown-item.vue │ │ ├── u-dropdown │ │ │ └── u-dropdown.vue │ │ ├── u-empty │ │ │ └── u-empty.vue │ │ ├── u-field │ │ │ └── u-field.vue │ │ ├── u-form-item │ │ │ └── u-form-item.vue │ │ ├── u-form │ │ │ └── u-form.vue │ │ ├── u-full-screen │ │ │ └── u-full-screen.vue │ │ ├── u-gap │ │ │ └── u-gap.vue │ │ ├── u-grid-item │ │ │ └── u-grid-item.vue │ │ ├── u-grid │ │ │ └── u-grid.vue │ │ ├── u-icon │ │ │ └── u-icon.vue │ │ ├── u-image │ │ │ └── u-image.vue │ │ ├── u-index-anchor │ │ │ └── u-index-anchor.vue │ │ ├── u-index-list │ │ │ └── u-index-list.vue │ │ ├── u-input │ │ │ └── u-input.vue │ │ ├── u-keyboard │ │ │ └── u-keyboard.vue │ │ ├── u-lazy-load │ │ │ └── u-lazy-load.vue │ │ ├── u-line-progress │ │ │ └── u-line-progress.vue │ │ ├── u-line │ │ │ └── u-line.vue │ │ ├── u-link │ │ │ └── u-link.vue │ │ ├── u-loading-page │ │ │ └── u-loading-page.vue │ │ ├── u-loading │ │ │ └── u-loading.vue │ │ ├── u-loadmore │ │ │ └── u-loadmore.vue │ │ ├── u-mask │ │ │ └── u-mask.vue │ │ ├── u-message-input │ │ │ └── u-message-input.vue │ │ ├── u-modal │ │ │ └── u-modal.vue │ │ ├── u-navbar │ │ │ └── u-navbar.vue │ │ ├── u-no-network │ │ │ └── u-no-network.vue │ │ ├── u-notice-bar │ │ │ └── u-notice-bar.vue │ │ ├── u-number-box │ │ │ └── u-number-box.vue │ │ ├── u-number-keyboard │ │ │ └── u-number-keyboard.vue │ │ ├── u-parse │ │ │ ├── libs │ │ │ │ ├── CssHandler.js │ │ │ │ ├── MpHtmlParser.js │ │ │ │ ├── config.js │ │ │ │ ├── handler.wxs │ │ │ │ └── trees.vue │ │ │ └── u-parse.vue │ │ ├── u-picker │ │ │ └── u-picker.vue │ │ ├── u-popup │ │ │ └── u-popup.vue │ │ ├── u-radio-group │ │ │ └── u-radio-group.vue │ │ ├── u-radio │ │ │ └── u-radio.vue │ │ ├── u-rate │ │ │ └── u-rate.vue │ │ ├── u-read-more │ │ │ └── u-read-more.vue │ │ ├── u-row-notice │ │ │ └── u-row-notice.vue │ │ ├── u-row │ │ │ └── u-row.vue │ │ ├── u-search │ │ │ └── u-search.vue │ │ ├── u-section │ │ │ └── u-section.vue │ │ ├── u-select │ │ │ └── u-select.vue │ │ ├── u-skeleton │ │ │ └── u-skeleton.vue │ │ ├── u-slider │ │ │ └── u-slider.vue │ │ ├── u-steps │ │ │ └── u-steps.vue │ │ ├── u-sticky │ │ │ └── u-sticky.vue │ │ ├── u-subsection │ │ │ └── u-subsection.vue │ │ ├── u-swipe-action │ │ │ └── u-swipe-action.vue │ │ ├── u-swiper │ │ │ └── u-swiper.vue │ │ ├── u-switch │ │ │ └── u-switch.vue │ │ ├── u-tabbar │ │ │ └── u-tabbar.vue │ │ ├── u-table │ │ │ └── u-table.vue │ │ ├── u-tabs-swiper │ │ │ └── u-tabs-swiper.vue │ │ ├── u-tabs │ │ │ └── u-tabs.vue │ │ ├── u-tag │ │ │ └── u-tag.vue │ │ ├── u-td │ │ │ └── u-td.vue │ │ ├── u-th │ │ │ └── u-th.vue │ │ ├── u-time-line-item │ │ │ └── u-time-line-item.vue │ │ ├── u-time-line │ │ │ └── u-time-line.vue │ │ ├── u-toast │ │ │ └── u-toast.vue │ │ ├── u-top-tips │ │ │ └── u-top-tips.vue │ │ ├── u-tr │ │ │ └── u-tr.vue │ │ ├── u-upload │ │ │ └── u-upload.vue │ │ ├── u-verification-code │ │ │ └── u-verification-code.vue │ │ └── u-waterfall │ │ │ └── u-waterfall.vue │ ├── iconfont.css │ ├── index.js │ ├── index.scss │ ├── libs │ │ ├── config │ │ │ ├── config.js │ │ │ └── zIndex.js │ │ ├── css │ │ │ ├── color.scss │ │ │ ├── common.scss │ │ │ ├── style.components.scss │ │ │ ├── style.h5.scss │ │ │ ├── style.mp.scss │ │ │ ├── style.nvue.scss │ │ │ └── style.vue.scss │ │ ├── function │ │ │ ├── $parent.js │ │ │ ├── addUnit.js │ │ │ ├── bem.js │ │ │ ├── color.js │ │ │ ├── colorGradient.js │ │ │ ├── debounce.js │ │ │ ├── deepClone.js │ │ │ ├── deepMerge.js │ │ │ ├── getParent.js │ │ │ ├── guid.js │ │ │ ├── md5.js │ │ │ ├── queryParams.js │ │ │ ├── random.js │ │ │ ├── randomArray.js │ │ │ ├── route.js │ │ │ ├── sys.js │ │ │ ├── test.js │ │ │ ├── throttle.js │ │ │ ├── timeFormat.js │ │ │ ├── timeFrom.js │ │ │ ├── toast.js │ │ │ ├── trim.js │ │ │ └── type2icon.js │ │ ├── mixin │ │ │ ├── mixin.js │ │ │ └── mpShare.js │ │ ├── request │ │ │ └── index.js │ │ ├── store │ │ │ └── index.js │ │ └── util │ │ │ ├── area.js │ │ │ ├── async-validator.js │ │ │ ├── city.js │ │ │ ├── emitter.js │ │ │ └── province.js │ ├── package.json │ └── theme.scss └── vue.config.js ├── docker-compose.yml ├── img ├── dict.png ├── docs.png ├── task.png ├── ticket.png ├── user.png └── wechat_group.jpg ├── server ├── .gitignore ├── Dockerfile ├── apps │ ├── crm │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── migrations │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── tests.py │ │ └── views.py │ ├── monitor │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── middleware.py │ │ ├── migrations │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── tests.py │ │ ├── urls.py │ │ └── views.py │ ├── system │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── authentication.py │ │ ├── filters.py │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_auto_20210718_0918.py │ │ │ ├── 0003_auto_20220227_1732.py │ │ │ └── __init__.py │ │ ├── mixins.py │ │ ├── models.py │ │ ├── permission.py │ │ ├── permission_data.py │ │ ├── serializers.py │ │ ├── signals.py │ │ ├── tasks.py │ │ ├── tests.py │ │ ├── urls.py │ │ └── views.py │ └── wf │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── filters.py │ │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ │ ├── models.py │ │ ├── scripts.py │ │ ├── serializers.py │ │ ├── services.py │ │ ├── tests.py │ │ ├── urls.py │ │ └── views.py ├── db.json ├── db.sqlite3 ├── log │ └── .gitignore ├── manage.py ├── media │ └── default │ │ └── avatar.png ├── requirements.txt ├── server │ ├── __init__.py │ ├── asgi.py │ ├── celery.py │ ├── conf_e.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── start.sh └── utils │ ├── __init__.py │ ├── model.py │ ├── pagination.py │ ├── queryset.py │ ├── response.py │ ├── serializer.py │ ├── test.py │ ├── view.py │ └── workflow.py └── specification.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.vue linguist-language=python 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | unpackage/dist/* 2 | node_modules/* 3 | deploy.sh 4 | package-lock.json 5 | .idea/ 6 | .vscode/ 7 | server/static/ 8 | img/wechat_group.jpg 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 blackholll 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /client/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | insert_final_newline = false 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /client/.env.development: -------------------------------------------------------------------------------- 1 | # just a flag 2 | ENV = 'development' 3 | 4 | # base api 5 | VUE_APP_BASE_API = 'http://localhost:8000/api' 6 | 7 | # vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable, 8 | # to control whether the babel-plugin-dynamic-import-node plugin is enabled. 9 | # It only does one thing by converting all import() to require(). 10 | # This configuration can significantly increase the speed of hot updates, 11 | # when you have a large number of pages. 12 | # Detail: https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.js 13 | 14 | VUE_CLI_BABEL_TRANSPILE_MODULES = true 15 | -------------------------------------------------------------------------------- /client/.env.production: -------------------------------------------------------------------------------- 1 | # just a flag 2 | ENV = 'production' 3 | 4 | # base api 5 | VUE_APP_BASE_API = 'http://47.95.0.242:1111/api' 6 | 7 | -------------------------------------------------------------------------------- /client/.env.staging: -------------------------------------------------------------------------------- 1 | NODE_ENV = production 2 | 3 | # just a flag 4 | ENV = 'staging' 5 | 6 | # base api 7 | VUE_APP_BASE_API = '/stage-api' 8 | 9 | -------------------------------------------------------------------------------- /client/.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | src/assets 3 | public 4 | dist 5 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | package-lock.json 8 | tests/**/coverage/ 9 | 10 | # Editor directories and files 11 | .idea 12 | .vscode 13 | *.suo 14 | *.ntvs* 15 | *.njsproj 16 | *.sln 17 | -------------------------------------------------------------------------------- /client/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 10 3 | script: npm run test 4 | notifications: 5 | email: false 6 | -------------------------------------------------------------------------------- /client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10-alpine3.9 as builder 2 | WORKDIR /code 3 | COPY . . 4 | RUN npm install --registry=https://registry.npm.taobao.org && npm run build:prod 5 | FROM nginx:1.19.2-alpine 6 | COPY --from=builder /code/dist /usr/share/nginx/html 7 | -------------------------------------------------------------------------------- /client/Dockerfile_dev: -------------------------------------------------------------------------------- 1 | FROM node:10-alpine3.9 2 | ENV NODE_ENV=development 3 | WORKDIR /code 4 | COPY . . 5 | RUN npm config set sass_binary_site=https://npm.taobao.org/mirrors/node-sass &&\ 6 | npm install --registry=https://registry.npm.taobao.org 7 | ENTRYPOINT ["npm","run","dev:docker"] 8 | -------------------------------------------------------------------------------- /client/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-present PanJiaChen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /client/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /client/build/index.js: -------------------------------------------------------------------------------- 1 | const { run } = require('runjs') 2 | const chalk = require('chalk') 3 | const config = require('../vue.config.js') 4 | const rawArgv = process.argv.slice(2) 5 | const args = rawArgv.join(' ') 6 | 7 | if (process.env.npm_config_preview || rawArgv.includes('--preview')) { 8 | const report = rawArgv.includes('--report') 9 | 10 | run(`vue-cli-service build ${args}`) 11 | 12 | const port = 9526 13 | const publicPath = config.publicPath 14 | 15 | var connect = require('connect') 16 | var serveStatic = require('serve-static') 17 | const app = connect() 18 | 19 | app.use( 20 | publicPath, 21 | serveStatic('./dist', { 22 | index: ['index.html', '/'] 23 | }) 24 | ) 25 | 26 | app.listen(port, function () { 27 | console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`)) 28 | if (report) { 29 | console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`)) 30 | } 31 | 32 | }) 33 | } else { 34 | run(`vue-cli-service build ${args}`) 35 | } 36 | -------------------------------------------------------------------------------- /client/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ['js', 'jsx', 'json', 'vue'], 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest', 5 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 6 | 'jest-transform-stub', 7 | '^.+\\.jsx?$': 'babel-jest' 8 | }, 9 | moduleNameMapper: { 10 | '^@/(.*)$': '/src/$1' 11 | }, 12 | snapshotSerializers: ['jest-serializer-vue'], 13 | testMatch: [ 14 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 15 | ], 16 | collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'], 17 | coverageDirectory: '/tests/unit/coverage', 18 | // 'collectCoverage': true, 19 | 'coverageReporters': [ 20 | 'lcov', 21 | 'text-summary' 22 | ], 23 | testURL: 'http://localhost/' 24 | } 25 | -------------------------------------------------------------------------------- /client/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@/*": ["src/*"] 6 | } 7 | }, 8 | "exclude": ["node_modules", "dist"] 9 | } 10 | -------------------------------------------------------------------------------- /client/mock/table.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | 3 | const data = Mock.mock({ 4 | 'items|30': [{ 5 | id: '@id', 6 | title: '@sentence(10, 20)', 7 | 'status|1': ['published', 'draft', 'deleted'], 8 | author: 'name', 9 | display_time: '@datetime', 10 | pageviews: '@integer(300, 5000)' 11 | }] 12 | }) 13 | 14 | export default [ 15 | { 16 | url: '/vue-admin-template/table/list', 17 | type: 'get', 18 | response: config => { 19 | const items = data.items 20 | return { 21 | code: 20000, 22 | data: { 23 | total: items.length, 24 | items: items 25 | } 26 | } 27 | } 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /client/postcss.config.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | 'plugins': { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | 'autoprefixer': {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= webpackConfig.name %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /client/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /client/src/api/dict.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getDictTypeList(query) { 4 | return request({ 5 | url: '/system/dicttype/', 6 | method: 'get', 7 | params: query 8 | }) 9 | } 10 | export function createDictType(data) { 11 | return request({ 12 | url: '/system/dicttype/', 13 | method: 'post', 14 | data 15 | }) 16 | } 17 | export function updateDictType(id, data) { 18 | return request({ 19 | url: `/system/dicttype/${id}/`, 20 | method: 'put', 21 | data 22 | }) 23 | } 24 | export function deleteDictType(id) { 25 | return request({ 26 | url: `/system/dicttype/${id}/`, 27 | method: 'delete' 28 | }) 29 | } 30 | 31 | export function getDictList(query) { 32 | return request({ 33 | url: '/system/dict/', 34 | method: 'get', 35 | params: query 36 | }) 37 | } 38 | export function createDict(data) { 39 | return request({ 40 | url: '/system/dict/', 41 | method: 'post', 42 | data 43 | }) 44 | } 45 | export function updateDict(id, data) { 46 | return request({ 47 | url: `/system/dict/${id}/`, 48 | method: 'put', 49 | data 50 | }) 51 | } 52 | export function deleteDict(id) { 53 | return request({ 54 | url: `/system/dict/${id}/`, 55 | method: 'delete' 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /client/src/api/file.js: -------------------------------------------------------------------------------- 1 | import { getToken } from "@/utils/auth" 2 | import request from '@/utils/request' 3 | 4 | export function upUrl() { 5 | return process.env.VUE_APP_BASE_API + '/file/' 6 | } 7 | 8 | export function upHeaders() { 9 | return { Authorization: "Bearer " + getToken() } 10 | } 11 | 12 | export function getFileList(query) { 13 | return request({ 14 | url: '/file/', 15 | method: 'get', 16 | params: query 17 | }) 18 | } -------------------------------------------------------------------------------- /client/src/api/moritor.js: -------------------------------------------------------------------------------- 1 | import { getToken } from "@/utils/auth" 2 | import request from '@/utils/request' 3 | 4 | //查看日志列表 5 | 6 | export function getlogList(query) { 7 | return request({ 8 | url: '/monitor/log/', 9 | method: 'get', 10 | params: query 11 | }) 12 | } 13 | //查看日志详情 14 | export function getLog(name) { 15 | return request({ 16 | url: `/monitor/log/${name}/`, 17 | method: 'get' 18 | }) 19 | } 20 | //获取服务器状态信息 21 | export function getServerList() { 22 | return request({ 23 | url: '/monitor/server/', 24 | method: 'get' 25 | }) 26 | } -------------------------------------------------------------------------------- /client/src/api/org.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getOrgAll() { 4 | return request({ 5 | url: '/system/organization/', 6 | method: 'get' 7 | }) 8 | } 9 | export function getOrgList(query) { 10 | return request({ 11 | url: '/system/organization/', 12 | method: 'get', 13 | params: query 14 | }) 15 | } 16 | export function createOrg(data) { 17 | return request({ 18 | url: '/system/organization/', 19 | method: 'post', 20 | data 21 | }) 22 | } 23 | export function updateOrg(id, data) { 24 | return request({ 25 | url: `/system/organization/${id}/`, 26 | method: 'put', 27 | data 28 | }) 29 | } 30 | export function deleteOrg(id) { 31 | return request({ 32 | url: `/system/organization/${id}/`, 33 | method: 'delete' 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /client/src/api/perm.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getPermAll() { 4 | return request({ 5 | url: '/system/permission/', 6 | method: 'get' 7 | }) 8 | } 9 | export function createPerm(data) { 10 | return request({ 11 | url: '/system/permission/', 12 | method: 'post', 13 | data 14 | }) 15 | } 16 | export function updatePerm(id, data) { 17 | return request({ 18 | url: `/system/permission/${id}/`, 19 | method: 'put', 20 | data 21 | }) 22 | } 23 | export function deletePerm(id) { 24 | return request({ 25 | url: `/system/permission/${id}/`, 26 | method: 'delete' 27 | }) 28 | } -------------------------------------------------------------------------------- /client/src/api/position.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getPositionAll() { 4 | return request({ 5 | url: '/system/position/', 6 | method: 'get' 7 | }) 8 | } 9 | 10 | export function createPosition(data) { 11 | return request({ 12 | url: '/system/position/', 13 | method: 'post', 14 | data 15 | }) 16 | } 17 | 18 | export function updatePosition(id, data) { 19 | return request({ 20 | url: `/system/position/${id}/`, 21 | method: 'put', 22 | data 23 | }) 24 | } 25 | 26 | export function deletePosition(id) { 27 | return request({ 28 | url: `/system/position/${id}/`, 29 | method: 'delete' 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /client/src/api/role.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getRoutes() { 4 | return request({ 5 | url: '/system/permission/', 6 | method: 'get' 7 | }) 8 | } 9 | 10 | export function getRoleAll() { 11 | return request({ 12 | url: '/system/role/', 13 | method: 'get' 14 | }) 15 | } 16 | 17 | export function createRole(data) { 18 | return request({ 19 | url: '/system/role/', 20 | method: 'post', 21 | data 22 | }) 23 | } 24 | 25 | export function updateRole(id, data) { 26 | return request({ 27 | url: `/system/role/${id}/`, 28 | method: 'put', 29 | data 30 | }) 31 | } 32 | 33 | export function deleteRole(id) { 34 | return request({ 35 | url: `/system/role/${id}/`, 36 | method: 'delete' 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /client/src/api/table.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getList(params) { 4 | return request({ 5 | url: '/vue-admin-template/table/list', 6 | method: 'get', 7 | params 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /client/src/api/task.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getptaskList(query) { 4 | return request({ 5 | url: '/system/ptask/', 6 | method: 'get', 7 | params: query 8 | }) 9 | } 10 | 11 | export function getTaskAll() { 12 | return request({ 13 | url: '/system/task/', 14 | method: 'get' 15 | }) 16 | } 17 | export function createptask(data) { 18 | return request({ 19 | url: '/system/ptask/', 20 | method: 'post', 21 | data 22 | }) 23 | } 24 | 25 | export function updateptask(id, data) { 26 | return request({ 27 | url: `/system/ptask/${id}/`, 28 | method: 'put', 29 | data 30 | }) 31 | } 32 | 33 | export function toggletask(id) { 34 | return request({ 35 | url: `/system/ptask/${id}/toggle/`, 36 | method: 'put' 37 | }) 38 | } 39 | 40 | export function deleteptask(id) { 41 | return request({ 42 | url: `/system/ptask/${id}/`, 43 | method: 'delete' 44 | }) 45 | } -------------------------------------------------------------------------------- /client/src/api/user.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function login(data) { 4 | return request({ 5 | url: '/token/', 6 | method: 'post', 7 | data 8 | }) 9 | } 10 | 11 | export function logout() { 12 | return request({ 13 | url: '/token/black/', 14 | method: 'get' 15 | }) 16 | } 17 | 18 | export function getInfo() { 19 | return request({ 20 | url: '/system/user/info/', 21 | method: 'get' 22 | }) 23 | } 24 | 25 | export function getUserList(query) { 26 | return request({ 27 | url: '/system/user/', 28 | method: 'get', 29 | params: query 30 | }) 31 | } 32 | 33 | export function getUser(id) { 34 | return request({ 35 | url: `/system/user/${id}/`, 36 | method: 'get' 37 | }) 38 | } 39 | 40 | export function createUser(data) { 41 | return request({ 42 | url: '/system/user/', 43 | method: 'post', 44 | data 45 | }) 46 | } 47 | 48 | export function updateUser(id, data) { 49 | return request({ 50 | url: `/system/user/${id}/`, 51 | method: 'put', 52 | data 53 | }) 54 | } 55 | 56 | export function deleteUser(id, data) { 57 | return request({ 58 | url: `/system/user/${id}/`, 59 | method: 'delete', 60 | data 61 | }) 62 | } 63 | 64 | export function changePassword(data) { 65 | return request({ 66 | url: '/system/user/password/', 67 | method: 'put', 68 | data 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /client/src/assets/404_images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/client/src/assets/404_images/404.png -------------------------------------------------------------------------------- /client/src/assets/404_images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/client/src/assets/404_images/404_cloud.png -------------------------------------------------------------------------------- /client/src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 32 | 33 | 45 | -------------------------------------------------------------------------------- /client/src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 47 | 48 | 63 | -------------------------------------------------------------------------------- /client/src/directive/el-table/adaptive.js: -------------------------------------------------------------------------------- 1 | import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event' 2 | 3 | /** 4 | * How to use 5 | * ... 6 | * el-table height is must be set 7 | * bottomOffset: 30(default) // The height of the table from the bottom of the page. 8 | */ 9 | 10 | const doResize = (el, binding, vnode) => { 11 | const { componentInstance: $table } = vnode 12 | 13 | const { value } = binding 14 | 15 | if (!$table.height) { 16 | throw new Error(`el-$table must set the height. Such as height='100px'`) 17 | } 18 | const bottomOffset = (value && value.bottomOffset) || 30 19 | 20 | if (!$table) return 21 | 22 | const height = window.innerHeight - el.getBoundingClientRect().top - bottomOffset 23 | $table.$nextTick(() => { 24 | $table.layout.setHeight(height) 25 | }) 26 | } 27 | 28 | export default { 29 | bind(el, binding, vnode) { 30 | el.resizeListener = () => { 31 | doResize(el, binding, vnode) 32 | } 33 | // parameter 1 is must be "Element" type 34 | addResizeListener(window.document.body, el.resizeListener) 35 | }, 36 | inserted(el, binding, vnode) { 37 | doResize(el, binding, vnode) 38 | }, 39 | unbind(el) { 40 | removeResizeListener(window.document.body, el.resizeListener) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /client/src/directive/el-table/index.js: -------------------------------------------------------------------------------- 1 | import adaptive from './adaptive' 2 | 3 | const install = function(Vue) { 4 | Vue.directive('el-height-adaptive-table', adaptive) 5 | } 6 | 7 | if (window.Vue) { 8 | window['el-height-adaptive-table'] = adaptive 9 | Vue.use(install); // eslint-disable-line 10 | } 11 | 12 | adaptive.install = install 13 | export default adaptive 14 | -------------------------------------------------------------------------------- /client/src/icons/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import SvgIcon from '@/components/SvgIcon'// svg component 3 | 4 | // register globally 5 | Vue.component('svg-icon', SvgIcon) 6 | 7 | const req = require.context('./svg', false, /\.svg$/) 8 | const requireAll = requireContext => requireContext.keys().map(requireContext) 9 | requireAll(req) 10 | -------------------------------------------------------------------------------- /client/src/icons/svg/404.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/bug.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/chart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/clipboard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/component.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/documentation.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/drag.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/education.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/email.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/example.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/excel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/exit-fullscreen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/eye-open.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/fullscreen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/guide.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/international.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/language.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/list.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/lock.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/message.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/money.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/nested.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/password.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/pdf.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/people.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/peoples.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/position.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/size.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/skill.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/star.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/theme.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/tree-table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/tree.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/wechat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svg/zip.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/icons/svgo.yml: -------------------------------------------------------------------------------- 1 | # replace default config 2 | 3 | # multipass: true 4 | # full: true 5 | 6 | plugins: 7 | 8 | # - name 9 | # 10 | # or: 11 | # - name: false 12 | # - name: true 13 | # 14 | # or: 15 | # - name: 16 | # param1: 1 17 | # param2: 2 18 | 19 | - removeAttrs: 20 | attrs: 21 | - 'fill' 22 | - 'fill-rule' 23 | -------------------------------------------------------------------------------- /client/src/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | 20 | 32 | 33 | 41 | -------------------------------------------------------------------------------- /client/src/layout/components/Sidebar/FixiOSBug.js: -------------------------------------------------------------------------------- 1 | export default { 2 | computed: { 3 | device() { 4 | return this.$store.state.app.device 5 | } 6 | }, 7 | mounted() { 8 | // In order to fix the click on menu on the ios device will trigger the mouseleave bug 9 | // https://github.com/PanJiaChen/vue-element-admin/issues/1135 10 | this.fixBugIniOS() 11 | }, 12 | methods: { 13 | fixBugIniOS() { 14 | const $subMenu = this.$refs.subMenu 15 | if ($subMenu) { 16 | const handleMouseleave = $subMenu.handleMouseleave 17 | $subMenu.handleMouseleave = (e) => { 18 | if (this.device === 'mobile') { 19 | return 20 | } 21 | handleMouseleave(e) 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /client/src/layout/components/Sidebar/Item.vue: -------------------------------------------------------------------------------- 1 | 30 | -------------------------------------------------------------------------------- /client/src/layout/components/Sidebar/Link.vue: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 37 | -------------------------------------------------------------------------------- /client/src/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 55 | -------------------------------------------------------------------------------- /client/src/layout/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navbar } from './Navbar' 2 | export { default as Sidebar } from './Sidebar' 3 | export { default as AppMain } from './AppMain' 4 | -------------------------------------------------------------------------------- /client/src/layout/mixin/ResizeHandler.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | const { body } = document 4 | const WIDTH = 992 // refer to Bootstrap's responsive design 5 | 6 | export default { 7 | watch: { 8 | $route(route) { 9 | if (this.device === 'mobile' && this.sidebar.opened) { 10 | store.dispatch('app/closeSideBar', { withoutAnimation: false }) 11 | } 12 | } 13 | }, 14 | beforeMount() { 15 | window.addEventListener('resize', this.$_resizeHandler) 16 | }, 17 | beforeDestroy() { 18 | window.removeEventListener('resize', this.$_resizeHandler) 19 | }, 20 | mounted() { 21 | const isMobile = this.$_isMobile() 22 | if (isMobile) { 23 | store.dispatch('app/toggleDevice', 'mobile') 24 | store.dispatch('app/closeSideBar', { withoutAnimation: true }) 25 | } 26 | }, 27 | methods: { 28 | // use $_ for mixins properties 29 | // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential 30 | $_isMobile() { 31 | const rect = body.getBoundingClientRect() 32 | return rect.width - 1 < WIDTH 33 | }, 34 | $_resizeHandler() { 35 | if (!document.hidden) { 36 | const isMobile = this.$_isMobile() 37 | store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop') 38 | 39 | if (isMobile) { 40 | store.dispatch('app/closeSideBar', { withoutAnimation: true }) 41 | } 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /client/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import 'normalize.css/normalize.css' // A modern alternative to CSS resets 4 | 5 | import ElementUI from 'element-ui' 6 | import 'element-ui/lib/theme-chalk/index.css' 7 | // import locale from 'element-ui/lib/locale/lang/en' // lang i18n 8 | 9 | import '@/styles/index.scss' // global css 10 | 11 | import App from './App' 12 | import store from './store' 13 | import router from './router' 14 | 15 | import '@/icons' // icon 16 | import '@/permission' // permission control 17 | import tableHeight from '@/directive/el-table/index' 18 | Vue.use(tableHeight) 19 | /** 20 | * If you don't want to use mock-server 21 | * you want to use MockJs for mock api 22 | * you can execute: mockXHR() 23 | * 24 | * Currently MockJs will be used in the production environment, 25 | * please remove it before going online ! ! ! 26 | */ 27 | if (process.env.NODE_ENV === 'production') { 28 | const { mockXHR } = require('../mock') 29 | mockXHR() 30 | } 31 | 32 | // set ElementUI lang to EN 33 | // Vue.use(ElementUI, { locale }) 34 | // 如果想要中文版 element-ui,按如下方式声明 35 | Vue.use(ElementUI, { size: 'medium' }) 36 | Vue.config.productionTip = false 37 | 38 | 39 | new Vue({ 40 | el: '#app', 41 | router, 42 | store, 43 | render: h => h(App) 44 | }) 45 | -------------------------------------------------------------------------------- /client/src/settings.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | title: '管理系统', 4 | 5 | /** 6 | * @type {boolean} true | false 7 | * @description Whether fix the header 8 | */ 9 | fixedHeader: false, 10 | 11 | /** 12 | * @type {boolean} true | false 13 | * @description Whether show the logo in sidebar 14 | */ 15 | sidebarLogo: true 16 | } 17 | -------------------------------------------------------------------------------- /client/src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | sidebar: state => state.app.sidebar, 3 | device: state => state.app.device, 4 | token: state => state.user.token, 5 | avatar: state => state.user.avatar, 6 | name: state => state.user.name, 7 | perms: state => state.user.perms, 8 | permission_routes: state => state.permission.routes 9 | } 10 | export default getters 11 | -------------------------------------------------------------------------------- /client/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import getters from './getters' 4 | import app from './modules/app' 5 | import permission from './modules/permission' 6 | import settings from './modules/settings' 7 | import user from './modules/user' 8 | 9 | Vue.use(Vuex) 10 | 11 | const store = new Vuex.Store({ 12 | modules: { 13 | app, 14 | permission, 15 | settings, 16 | user 17 | }, 18 | getters 19 | }) 20 | 21 | export default store 22 | -------------------------------------------------------------------------------- /client/src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const state = { 4 | sidebar: { 5 | opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true, 6 | withoutAnimation: false 7 | }, 8 | device: 'desktop' 9 | } 10 | 11 | const mutations = { 12 | TOGGLE_SIDEBAR: state => { 13 | state.sidebar.opened = !state.sidebar.opened 14 | state.sidebar.withoutAnimation = false 15 | if (state.sidebar.opened) { 16 | Cookies.set('sidebarStatus', 1) 17 | } else { 18 | Cookies.set('sidebarStatus', 0) 19 | } 20 | }, 21 | CLOSE_SIDEBAR: (state, withoutAnimation) => { 22 | Cookies.set('sidebarStatus', 0) 23 | state.sidebar.opened = false 24 | state.sidebar.withoutAnimation = withoutAnimation 25 | }, 26 | TOGGLE_DEVICE: (state, device) => { 27 | state.device = device 28 | } 29 | } 30 | 31 | const actions = { 32 | toggleSideBar({ commit }) { 33 | commit('TOGGLE_SIDEBAR') 34 | }, 35 | closeSideBar({ commit }, { withoutAnimation }) { 36 | commit('CLOSE_SIDEBAR', withoutAnimation) 37 | }, 38 | toggleDevice({ commit }, device) { 39 | commit('TOGGLE_DEVICE', device) 40 | } 41 | } 42 | 43 | export default { 44 | namespaced: true, 45 | state, 46 | mutations, 47 | actions 48 | } 49 | -------------------------------------------------------------------------------- /client/src/store/modules/permission.js: -------------------------------------------------------------------------------- 1 | import { asyncRoutes, constantRoutes } from '@/router' 2 | 3 | /** 4 | * Use meta.perm to determine if the current user has permission 5 | * @param perms 6 | * @param route 7 | */ 8 | function hasPermission(perms, route) { 9 | if (route.meta && route.meta.perms) { 10 | return perms.some(perm => route.meta.perms.includes(perm)) 11 | } else { 12 | return true 13 | } 14 | } 15 | 16 | /** 17 | * Filter asynchronous routing tables by recursion 18 | * @param routes asyncRoutes 19 | * @param perms 20 | */ 21 | export function filterAsyncRoutes(routes, perms) { 22 | const res = [] 23 | 24 | routes.forEach(route => { 25 | const tmp = { ...route } 26 | if (hasPermission(perms, tmp)) { 27 | if (tmp.children) { 28 | tmp.children = filterAsyncRoutes(tmp.children, perms) 29 | } 30 | res.push(tmp) 31 | } 32 | }) 33 | return res 34 | } 35 | 36 | const state = { 37 | routes: [], 38 | addRoutes: [] 39 | } 40 | 41 | const mutations = { 42 | SET_ROUTES: (state, routes) => { 43 | state.addRoutes = routes 44 | state.routes = constantRoutes.concat(routes) 45 | } 46 | } 47 | 48 | const actions = { 49 | generateRoutes({ commit }, perms) { 50 | return new Promise(resolve => { 51 | let accessedRoutes 52 | if (perms.includes('admin')) { 53 | accessedRoutes = asyncRoutes || [] 54 | } else { 55 | accessedRoutes = filterAsyncRoutes(asyncRoutes, perms) 56 | } 57 | commit('SET_ROUTES', accessedRoutes) 58 | resolve(accessedRoutes) 59 | }) 60 | } 61 | } 62 | 63 | export default { 64 | namespaced: true, 65 | state, 66 | mutations, 67 | actions 68 | } 69 | -------------------------------------------------------------------------------- /client/src/store/modules/settings.js: -------------------------------------------------------------------------------- 1 | import defaultSettings from '@/settings' 2 | 3 | const { showSettings, fixedHeader, sidebarLogo } = defaultSettings 4 | 5 | const state = { 6 | showSettings: showSettings, 7 | fixedHeader: fixedHeader, 8 | sidebarLogo: sidebarLogo 9 | } 10 | 11 | const mutations = { 12 | CHANGE_SETTING: (state, { key, value }) => { 13 | if (state.hasOwnProperty(key)) { 14 | state[key] = value 15 | } 16 | } 17 | } 18 | 19 | const actions = { 20 | changeSetting({ commit }, data) { 21 | commit('CHANGE_SETTING', data) 22 | } 23 | } 24 | 25 | export default { 26 | namespaced: true, 27 | state, 28 | mutations, 29 | actions 30 | } 31 | 32 | -------------------------------------------------------------------------------- /client/src/styles/element-ui.scss: -------------------------------------------------------------------------------- 1 | // cover some element-ui styles 2 | 3 | .el-breadcrumb__inner, 4 | .el-breadcrumb__inner a { 5 | font-weight: 400 !important; 6 | } 7 | 8 | .el-upload { 9 | input[type="file"] { 10 | display: none !important; 11 | } 12 | } 13 | 14 | .el-upload__input { 15 | display: none; 16 | } 17 | 18 | 19 | // to fixed https://github.com/ElemeFE/element/issues/2461 20 | .el-dialog { 21 | transform: none; 22 | left: 0; 23 | position: relative; 24 | margin: 0 auto; 25 | } 26 | 27 | // refine element ui upload 28 | .upload-container { 29 | .el-upload { 30 | width: 100%; 31 | 32 | .el-upload-dragger { 33 | width: 100%; 34 | height: 200px; 35 | } 36 | } 37 | } 38 | 39 | // dropdown 40 | .el-dropdown-menu { 41 | a { 42 | display: block 43 | } 44 | } 45 | 46 | // to fix el-date-picker css style 47 | .el-range-separator { 48 | box-sizing: content-box; 49 | } 50 | -------------------------------------------------------------------------------- /client/src/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin clearfix { 2 | &:after { 3 | content: ""; 4 | display: table; 5 | clear: both; 6 | } 7 | } 8 | 9 | @mixin scrollBar { 10 | &::-webkit-scrollbar-track-piece { 11 | background: #d3dce6; 12 | } 13 | 14 | &::-webkit-scrollbar { 15 | width: 6px; 16 | } 17 | 18 | &::-webkit-scrollbar-thumb { 19 | background: #99a9bf; 20 | border-radius: 20px; 21 | } 22 | } 23 | 24 | @mixin relative { 25 | position: relative; 26 | width: 100%; 27 | height: 100%; 28 | } 29 | -------------------------------------------------------------------------------- /client/src/styles/transition.scss: -------------------------------------------------------------------------------- 1 | // global transition css 2 | 3 | /* fade */ 4 | .fade-enter-active, 5 | .fade-leave-active { 6 | transition: opacity 0.28s; 7 | } 8 | 9 | .fade-enter, 10 | .fade-leave-active { 11 | opacity: 0; 12 | } 13 | 14 | /* fade-transform */ 15 | .fade-transform-leave-active, 16 | .fade-transform-enter-active { 17 | transition: all .5s; 18 | } 19 | 20 | .fade-transform-enter { 21 | opacity: 0; 22 | transform: translateX(-30px); 23 | } 24 | 25 | .fade-transform-leave-to { 26 | opacity: 0; 27 | transform: translateX(30px); 28 | } 29 | 30 | /* breadcrumb transition */ 31 | .breadcrumb-enter-active, 32 | .breadcrumb-leave-active { 33 | transition: all .5s; 34 | } 35 | 36 | .breadcrumb-enter, 37 | .breadcrumb-leave-active { 38 | opacity: 0; 39 | transform: translateX(20px); 40 | } 41 | 42 | .breadcrumb-move { 43 | transition: all .5s; 44 | } 45 | 46 | .breadcrumb-leave-active { 47 | position: absolute; 48 | } 49 | -------------------------------------------------------------------------------- /client/src/styles/variables.scss: -------------------------------------------------------------------------------- 1 | // sidebar 2 | $menuText:#bfcbd9; 3 | $menuActiveText:#409EFF; 4 | $subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951 5 | 6 | $menuBg:#304156; 7 | $menuHover:#263445; 8 | 9 | $subMenuBg:#1f2d3d; 10 | $subMenuHover:#001528; 11 | 12 | $sideBarWidth: 210px; 13 | 14 | // the :export directive is the magic sauce for webpack 15 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass 16 | :export { 17 | menuText: $menuText; 18 | menuActiveText: $menuActiveText; 19 | subMenuActiveText: $subMenuActiveText; 20 | menuBg: $menuBg; 21 | menuHover: $menuHover; 22 | subMenuBg: $subMenuBg; 23 | subMenuHover: $subMenuHover; 24 | sideBarWidth: $sideBarWidth; 25 | } 26 | -------------------------------------------------------------------------------- /client/src/utils/auth.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const TokenKey = 'token' 4 | 5 | export function getToken() { 6 | return Cookies.get(TokenKey) 7 | } 8 | 9 | export function setToken(token) { 10 | return Cookies.set(TokenKey, token) 11 | } 12 | 13 | export function removeToken() { 14 | return Cookies.remove(TokenKey) 15 | } 16 | 17 | // export function refreshToken() { 18 | // let token = getToken() 19 | // let data = {"token": token} 20 | // return request({ 21 | // url: '/token/refresh/', 22 | // method: 'post', 23 | // data 24 | // }) 25 | // } 26 | -------------------------------------------------------------------------------- /client/src/utils/get-page-title.js: -------------------------------------------------------------------------------- 1 | import defaultSettings from '@/settings' 2 | 3 | const title = defaultSettings.title || '认证系统' 4 | 5 | export default function getPageTitle(pageTitle) { 6 | if (pageTitle) { 7 | return `${pageTitle} - ${title}` 8 | } 9 | return `${title}` 10 | } 11 | -------------------------------------------------------------------------------- /client/src/utils/permission.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | /** 4 | * @param {Array} value 5 | * @returns {Boolean} 6 | * @example see @/views/permission/directive.vue 7 | */ 8 | export default function checkPermission(value) { 9 | if (value && value instanceof Array && value.length > 0) { 10 | const perms = store.getters && store.getters.perms 11 | const permissionperms = value 12 | if (perms.includes('admin')) { 13 | return true 14 | } // 如果是超管,都可以操作 15 | const hasPermission = perms.some(perm => { 16 | return permissionperms.includes(perm) 17 | }) 18 | 19 | if (!hasPermission) { 20 | return false 21 | } 22 | return true 23 | } else { 24 | console.error(`need perms! Like v-permission="['admin','editor']"`) 25 | return false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /client/src/utils/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by PanJiaChen on 16/11/18. 3 | */ 4 | 5 | /** 6 | * @param {string} path 7 | * @returns {Boolean} 8 | */ 9 | export function isExternal(path) { 10 | return /^(https?:|mailto:|tel:)/.test(path) 11 | } 12 | 13 | /** 14 | * @param {string} str 15 | * @returns {Boolean} 16 | */ 17 | export function validUsername(str) { 18 | const valid_map = ['admin', 'editor'] 19 | return valid_map.indexOf(str.trim()) >= 0 20 | } 21 | -------------------------------------------------------------------------------- /client/src/vendor/Export2Zip.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { saveAs } from 'file-saver' 3 | import JSZip from 'jszip' 4 | 5 | export function export_txt_to_zip(th, jsonData, txtName, zipName) { 6 | const zip = new JSZip() 7 | const txt_name = txtName || 'file' 8 | const zip_name = zipName || 'file' 9 | const data = jsonData 10 | let txtData = `${th}\r\n` 11 | data.forEach((row) => { 12 | let tempStr = '' 13 | tempStr = row.toString() 14 | txtData += `${tempStr}\r\n` 15 | }) 16 | zip.file(`${txt_name}.txt`, txtData) 17 | zip.generateAsync({ 18 | type: "blob" 19 | }).then((blob) => { 20 | saveAs(blob, `${zip_name}.zip`) 21 | }, (err) => { 22 | alert('导出失败') 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /client/src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 21 | 22 | 33 | -------------------------------------------------------------------------------- /client/src/views/nested/menu1/index.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /client/src/views/nested/menu1/menu1-1/index.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /client/src/views/nested/menu1/menu1-2/index.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /client/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /client/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /client/src/views/nested/menu1/menu1-3/index.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /client/src/views/nested/menu2/index.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /client/src/views/tree/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 78 | 79 | -------------------------------------------------------------------------------- /client/src/views/workflow/configuration.vue: -------------------------------------------------------------------------------- 1 | 17 | 53 | -------------------------------------------------------------------------------- /client/tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /client/tests/unit/components/Hamburger.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import Hamburger from '@/components/Hamburger/index.vue' 3 | describe('Hamburger.vue', () => { 4 | it('toggle click', () => { 5 | const wrapper = shallowMount(Hamburger) 6 | const mockFn = jest.fn() 7 | wrapper.vm.$on('toggleClick', mockFn) 8 | wrapper.find('.hamburger').trigger('click') 9 | expect(mockFn).toBeCalled() 10 | }) 11 | it('prop isActive', () => { 12 | const wrapper = shallowMount(Hamburger) 13 | wrapper.setProps({ isActive: true }) 14 | expect(wrapper.contains('.is-active')).toBe(true) 15 | wrapper.setProps({ isActive: false }) 16 | expect(wrapper.contains('.is-active')).toBe(false) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /client/tests/unit/components/SvgIcon.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import SvgIcon from '@/components/SvgIcon/index.vue' 3 | describe('SvgIcon.vue', () => { 4 | it('iconClass', () => { 5 | const wrapper = shallowMount(SvgIcon, { 6 | propsData: { 7 | iconClass: 'test' 8 | } 9 | }) 10 | expect(wrapper.find('use').attributes().href).toBe('#icon-test') 11 | }) 12 | it('className', () => { 13 | const wrapper = shallowMount(SvgIcon, { 14 | propsData: { 15 | iconClass: 'test' 16 | } 17 | }) 18 | expect(wrapper.classes().length).toBe(1) 19 | wrapper.setProps({ className: 'test' }) 20 | expect(wrapper.classes().includes('test')).toBe(true) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /client/tests/unit/utils/formatTime.spec.js: -------------------------------------------------------------------------------- 1 | import { formatTime } from '@/utils/index.js' 2 | 3 | describe('Utils:formatTime', () => { 4 | const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01" 5 | const retrofit = 5 * 1000 6 | 7 | it('ten digits timestamp', () => { 8 | expect(formatTime((d / 1000).toFixed(0))).toBe('7月13日17时54分') 9 | }) 10 | it('test now', () => { 11 | expect(formatTime(+new Date() - 1)).toBe('刚刚') 12 | }) 13 | it('less two minute', () => { 14 | expect(formatTime(+new Date() - 60 * 2 * 1000 + retrofit)).toBe('2分钟前') 15 | }) 16 | it('less two hour', () => { 17 | expect(formatTime(+new Date() - 60 * 60 * 2 * 1000 + retrofit)).toBe('2小时前') 18 | }) 19 | it('less one day', () => { 20 | expect(formatTime(+new Date() - 60 * 60 * 24 * 1 * 1000)).toBe('1天前') 21 | }) 22 | it('more than one day', () => { 23 | expect(formatTime(d)).toBe('7月13日17时54分') 24 | }) 25 | it('format', () => { 26 | expect(formatTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54') 27 | expect(formatTime(d, '{y}-{m}-{d}')).toBe('2018-07-13') 28 | expect(formatTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54') 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /client/tests/unit/utils/parseTime.spec.js: -------------------------------------------------------------------------------- 1 | import { parseTime } from '@/utils/index.js' 2 | 3 | describe('Utils:parseTime', () => { 4 | const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01" 5 | it('timestamp', () => { 6 | expect(parseTime(d)).toBe('2018-07-13 17:54:01') 7 | }) 8 | it('ten digits timestamp', () => { 9 | expect(parseTime((d / 1000).toFixed(0))).toBe('2018-07-13 17:54:01') 10 | }) 11 | it('new Date', () => { 12 | expect(parseTime(new Date(d))).toBe('2018-07-13 17:54:01') 13 | }) 14 | it('format', () => { 15 | expect(parseTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54') 16 | expect(parseTime(d, '{y}-{m}-{d}')).toBe('2018-07-13') 17 | expect(parseTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54') 18 | }) 19 | it('get the day of the week', () => { 20 | expect(parseTime(d, '{a}')).toBe('五') // 星期五 21 | }) 22 | it('get the day of the week', () => { 23 | expect(parseTime(+d + 1000 * 60 * 60 * 24 * 2, '{a}')).toBe('日') // 星期日 24 | }) 25 | it('empty argument', () => { 26 | expect(parseTime()).toBeNull() 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /client/tests/unit/utils/validate.spec.js: -------------------------------------------------------------------------------- 1 | import { validUsername, isExternal } from '@/utils/validate.js' 2 | 3 | describe('Utils:validate', () => { 4 | it('validUsername', () => { 5 | expect(validUsername('admin')).toBe(true) 6 | expect(validUsername('editor')).toBe(true) 7 | expect(validUsername('xxxx')).toBe(false) 8 | }) 9 | it('isExternal', () => { 10 | expect(isExternal('https://github.com/PanJiaChen/vue-element-admin')).toBe(true) 11 | expect(isExternal('http://github.com/PanJiaChen/vue-element-admin')).toBe(true) 12 | expect(isExternal('github.com/PanJiaChen/vue-element-admin')).toBe(false) 13 | expect(isExternal('/dashboard')).toBe(false) 14 | expect(isExternal('./dashboard')).toBe(false) 15 | expect(isExternal('dashboard')).toBe(false) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /client_mp/.gitignore: -------------------------------------------------------------------------------- 1 | unpackage/dist/* 2 | node_modules/* 3 | deploy.sh 4 | package-lock.json -------------------------------------------------------------------------------- /client_mp/App.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | -------------------------------------------------------------------------------- /client_mp/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 www.uviewui.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /client_mp/common/demo.scss: -------------------------------------------------------------------------------- 1 | /* #ifndef APP-NVUE */ 2 | view, 3 | text { 4 | box-sizing: border-box; 5 | } 6 | /* #endif */ 7 | 8 | /* start--演示页面使用的统一样式--start */ 9 | .u-demo { 10 | padding: 25px 20px; 11 | } 12 | 13 | .u-demo-wrap { 14 | border-width: 1px; 15 | border-color: #ddd; 16 | border-style: dashed; 17 | background-color: rgb(250, 250, 250); 18 | padding: 20px 10px; 19 | border-radius: 3px; 20 | } 21 | 22 | .u-demo-area { 23 | text-align: center; 24 | } 25 | 26 | .u-no-demo-here { 27 | color: $u-tips-color; 28 | font-size: 13px; 29 | } 30 | 31 | .u-demo-result-line { 32 | border-width: 1px; 33 | border-color: #ddd; 34 | border-style: dashed; 35 | padding: 5px 20px; 36 | margin-top: 30px; 37 | border-radius: 5px; 38 | background-color: rgb(240, 240, 240); 39 | color: $u-content-color; 40 | font-size: 16px; 41 | /* #ifndef APP-NVUE */ 42 | word-break: break-word; 43 | display: inline-block; 44 | /* #endif */ 45 | text-align: left; 46 | 47 | } 48 | 49 | .u-demo-title, 50 | .u-config-title { 51 | text-align: center; 52 | font-size: 16px; 53 | font-weight: bold; 54 | margin-bottom: 20px; 55 | } 56 | 57 | .u-config-item { 58 | margin-top: 25px; 59 | } 60 | 61 | .u-config-title { 62 | margin-top: 20px; 63 | padding-bottom: 5px; 64 | } 65 | 66 | .u-item-title { 67 | position: relative; 68 | font-size: 15px; 69 | padding-left: 8px; 70 | line-height: 1; 71 | margin-bottom: 11px; 72 | } 73 | 74 | .u-item-title:after { 75 | position: absolute; 76 | width: 4px; 77 | top: -1px; 78 | height: 16px; 79 | /* #ifndef APP-NVUE */ 80 | content: ''; 81 | /* #endif */ 82 | left: 0; 83 | border-radius: 10px; 84 | background-color: $u-content-color; 85 | } 86 | /* end--演示页面使用的统一样式--end */ 87 | -------------------------------------------------------------------------------- /client_mp/common/http.api.js: -------------------------------------------------------------------------------- 1 | // 如果没有通过拦截器配置域名的话,可以在这里写上完整的URL(加上域名部分) 2 | let hotSearchUrl = '/ebapi/store_api/hot_search'; 3 | let indexUrl = '/ebapi/public_api/index'; 4 | 5 | // 此处第二个参数vm,就是我们在页面使用的this,你可以通过vm获取vuex等操作,更多内容详见uView对拦截器的介绍部分: 6 | // https://uviewui.com/js/http.html#%E4%BD%95%E8%B0%93%E8%AF%B7%E6%B1%82%E6%8B%A6%E6%88%AA%EF%BC%9F 7 | const install = (Vue, vm) => { 8 | // 此处没有使用传入的params参数 9 | let getSearch = (params = {}) => vm.$u.get(hotSearchUrl, { 10 | id: 2 11 | }); 12 | // 此处使用了传入的params参数,一切自定义即可 13 | let getInfo = (params = {}) => vm.$u.post(indexUrl, params); 14 | 15 | let gettest = (params = {}) => vm.$u.get('/system/test/', params); 16 | // 将各个定义的接口名称,统一放进对象挂载到vm.$u.api(因为vm就是this,也即this.$u.api)下 17 | vm.$u.api = {getSearch, getInfo, gettest}; 18 | } 19 | 20 | export default { 21 | install 22 | } -------------------------------------------------------------------------------- /client_mp/common/locales/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // 可以以页面为单位来写,比如首页的内容,写在index字段,个人中心写在center,共同部分写在common部分 3 | components: { 4 | desc: 'Numerous components cover the various requirements of the development process, and the components are rich in functions and compatible with multiple terminals. Let you integrate quickly, out of the box' 5 | }, 6 | js: { 7 | desc: 'Numerous intimate gadgets are a weapon that you can call upon during the development process, allowing you to dart in your hand and pierce the Yang with a hundred steps' 8 | }, 9 | template: { 10 | desc: 'Collection of many commonly used pages and layouts, reducing the repetitive work of developers, allowing you to focus on logic and get twice the result with half the effort' 11 | }, 12 | nav: { 13 | components: 'Components', 14 | js: 'JS', 15 | template: 'Template' 16 | }, 17 | common: { 18 | intro: 'UI framework for rapid development of multiple platforms', 19 | title: 'uView UI', 20 | }, 21 | } -------------------------------------------------------------------------------- /client_mp/common/locales/zh.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // 可以以页面为单位来写,比如首页的内容,写在index字段,个人中心写在center,共同部分写在common部分 3 | components: { 4 | desc: '众多组件覆盖开发过程的各个需求,组件功能丰富,多端兼容。让你快速集成,开箱即用' 5 | }, 6 | js: { 7 | desc: '众多的贴心小工具,是你开发过程中召之即来的利器,让你飞镖在手,百步穿杨' 8 | }, 9 | template: { 10 | desc: '收集众多的常用页面和布局,减少开发者的重复工作,让你专注逻辑,事半功倍' 11 | }, 12 | nav: { 13 | components: '组件', 14 | js: '工具', 15 | template: '模板' 16 | }, 17 | common: { 18 | intro: '多平台快速开发的UI框架', 19 | title: 'uView UI', 20 | }, 21 | } -------------------------------------------------------------------------------- /client_mp/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App'; 3 | 4 | Vue.config.productionTip = false; 5 | 6 | App.mpType = 'app'; 7 | 8 | // 此处为演示Vue.prototype使用,非uView的功能部分 9 | Vue.prototype.vuePrototype = '枣红'; 10 | 11 | // 引入全局uView 12 | import uView from 'uview-ui'; 13 | Vue.use(uView); 14 | 15 | // 此处为演示vuex使用,非uView的功能部分 16 | import store from '@/store'; 17 | 18 | // 引入uView提供的对vuex的简写法文件 19 | let vuexStore = require('@/store/$u.mixin.js'); 20 | Vue.mixin(vuexStore); 21 | 22 | // 引入uView对小程序分享的mixin封装 23 | let mpShare = require('uview-ui/libs/mixin/mpShare.js'); 24 | Vue.mixin(mpShare); 25 | 26 | // i18n部分的配置 27 | // 引入语言包,注意路径 28 | import Chinese from '@/common/locales/zh.js'; 29 | import English from '@/common/locales/en.js'; 30 | 31 | // VueI18n 32 | import VueI18n from '@/common/vue-i18n.min.js'; 33 | 34 | // VueI18n 35 | Vue.use(VueI18n); 36 | 37 | const i18n = new VueI18n({ 38 | // 默认语言 39 | locale: 'zh', 40 | // 引入语言文件 41 | messages: { 42 | 'zh': Chinese, 43 | 'en': English, 44 | } 45 | }); 46 | 47 | // 由于微信小程序的运行机制问题,需声明如下一行,H5和APP非必填 48 | Vue.prototype._i18n = i18n; 49 | 50 | const app = new Vue({ 51 | i18n, 52 | store, 53 | ...App 54 | }); 55 | 56 | // http拦截器,将此部分放在new Vue()和app.$mount()之间,才能App.vue中正常使用 57 | import httpInterceptor from '@/common/http.interceptor.js'; 58 | Vue.use(httpInterceptor, app); 59 | 60 | // http接口API抽离,免于写url或者一些固定的参数 61 | import httpApi from '@/common/http.api.js'; 62 | Vue.use(httpApi, app); 63 | 64 | app.$mount(); 65 | -------------------------------------------------------------------------------- /client_mp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uView", 3 | "version": "1.0.0", 4 | "description": "

\r \"logo\"\r

\r

uView

\r

多平台快速开发的UI框架

", 5 | "main": "main.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/YanxinNet/uView.git" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/YanxinNet/uView/issues" 18 | }, 19 | "homepage": "https://github.com/YanxinNet/uView#readme", 20 | "dependencies": { 21 | "vue-i18n": "^8.20.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client_mp/pages/componentsA/fullScreen/index.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 46 | 47 | 50 | -------------------------------------------------------------------------------- /client_mp/pages/componentsA/indexList/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 30 | 31 | 44 | -------------------------------------------------------------------------------- /client_mp/pages/componentsA/topTips/index.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 54 | 55 | -------------------------------------------------------------------------------- /client_mp/pages/componentsC/circleProgress/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 40 | 41 | 66 | -------------------------------------------------------------------------------- /client_mp/pages/componentsC/link/index.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 51 | 52 | 55 | -------------------------------------------------------------------------------- /client_mp/pages/componentsC/test/index.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client_mp/pages/home/home.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 51 | 52 | 55 | -------------------------------------------------------------------------------- /client_mp/pages/library/color/index.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 51 | 52 | 55 | -------------------------------------------------------------------------------- /client_mp/pages/library/deepClone/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 34 | 35 | 38 | -------------------------------------------------------------------------------- /client_mp/pages/library/deepMerge/index.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 71 | 72 | 75 | -------------------------------------------------------------------------------- /client_mp/pages/library/globalVariable/globalData.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 46 | 47 | 50 | -------------------------------------------------------------------------------- /client_mp/pages/library/globalVariable/prototype.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 45 | 46 | 49 | -------------------------------------------------------------------------------- /client_mp/pages/library/globalVariable/vuex.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 37 | 38 | -------------------------------------------------------------------------------- /client_mp/pages/library/http/index.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 57 | 58 | 61 | -------------------------------------------------------------------------------- /client_mp/pages/library/md5/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 43 | 44 | 47 | -------------------------------------------------------------------------------- /client_mp/pages/library/mpShare/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 16 | 17 | 20 | -------------------------------------------------------------------------------- /client_mp/pages/library/queryParams/index.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 50 | 51 | 54 | -------------------------------------------------------------------------------- /client_mp/pages/library/random/index.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 55 | 56 | 61 | -------------------------------------------------------------------------------- /client_mp/pages/library/randomArray/index.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 44 | 45 | 48 | -------------------------------------------------------------------------------- /client_mp/pages/library/route/routeTo.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 42 | 43 | 52 | -------------------------------------------------------------------------------- /client_mp/pages/library/timeFormat/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 55 | 56 | 59 | -------------------------------------------------------------------------------- /client_mp/pages/library/trim/index.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 59 | 60 | 63 | -------------------------------------------------------------------------------- /client_mp/pages/template/citySelect/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 43 | 44 | 49 | -------------------------------------------------------------------------------- /client_mp/pages/template/douyin/index.nvue: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /client_mp/pages/template/submitBar/index.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 30 | 31 | 74 | -------------------------------------------------------------------------------- /client_mp/pages/uview/uview.config.js: -------------------------------------------------------------------------------- 1 | 2 | export default [{ 3 | groupName: '组件', 4 | groupName_en: 'components', 5 | list: [{ 6 | path: '/pages/example/components', 7 | icon: 'color', 8 | title: 'components 组件', 9 | title_en: 'components', 10 | }] 11 | }, 12 | { 13 | groupName: '工具', 14 | groupName_en: 'tools', 15 | list: [{ 16 | path: '/pages/example/js', 17 | icon: 'http', 18 | title: 'tool 工具', 19 | title_en: 'Tool', 20 | }] 21 | }, 22 | { 23 | groupName: '模板', 24 | groupName_en: 'templates', 25 | list: [{ 26 | path: '/pages/example/template', 27 | icon: 'wxCenter', 28 | title: 'template 模板', 29 | title_en: 'template', 30 | }] 31 | } 32 | ] 33 | -------------------------------------------------------------------------------- /client_mp/static/uview/common/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/client_mp/static/uview/common/favicon.ico -------------------------------------------------------------------------------- /client_mp/static/uview/common/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/client_mp/static/uview/common/logo.png -------------------------------------------------------------------------------- /client_mp/static/uview/example/component.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/client_mp/static/uview/example/component.png -------------------------------------------------------------------------------- /client_mp/static/uview/example/component_select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/client_mp/static/uview/example/component_select.png -------------------------------------------------------------------------------- /client_mp/static/uview/example/js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/client_mp/static/uview/example/js.png -------------------------------------------------------------------------------- /client_mp/static/uview/example/js_bak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/client_mp/static/uview/example/js_bak.png -------------------------------------------------------------------------------- /client_mp/static/uview/example/js_select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/client_mp/static/uview/example/js_select.png -------------------------------------------------------------------------------- /client_mp/static/uview/example/js_select_bak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/client_mp/static/uview/example/js_select_bak.png -------------------------------------------------------------------------------- /client_mp/static/uview/example/min_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/client_mp/static/uview/example/min_button.png -------------------------------------------------------------------------------- /client_mp/static/uview/example/min_button_select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/client_mp/static/uview/example/min_button_select.png -------------------------------------------------------------------------------- /client_mp/static/uview/example/template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/client_mp/static/uview/example/template.png -------------------------------------------------------------------------------- /client_mp/static/uview/example/template_select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/client_mp/static/uview/example/template_select.png -------------------------------------------------------------------------------- /client_mp/store/$u.mixin.js: -------------------------------------------------------------------------------- 1 | import { mapState } from 'vuex' 2 | import store from "@/store" 3 | 4 | // 尝试将用户在根目录中的store/index.js的vuex的state变量,全部加载到全局变量中 5 | let $uStoreKey = []; 6 | try{ 7 | $uStoreKey = store.state ? Object.keys(store.state) : []; 8 | }catch(e){ 9 | 10 | } 11 | 12 | module.exports = { 13 | beforeCreate() { 14 | // 将vuex方法挂在到$u中 15 | // 使用方法为:如果要修改vuex的state中的user.name变量为"史诗" => this.$u.vuex('user.name', '史诗') 16 | // 如果要修改vuex的state的version变量为1.0.1 => this.$u.vuex('version', '1.0.1') 17 | this.$u.vuex = (name, value) => { 18 | this.$store.commit('$uStore', { 19 | name,value 20 | }) 21 | } 22 | }, 23 | computed: { 24 | // 将vuex的state中的所有变量,解构到全局混入的mixin中 25 | ...mapState($uStoreKey) 26 | } 27 | } -------------------------------------------------------------------------------- /client_mp/template.h5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%= htmlWebpackPlugin.options.title %> 10 | 11 | 12 | 13 | 16 | 21 | 22 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 36 |
37 | 38 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /client_mp/uni.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 下方引入的为uView UI的集成样式文件,为scss预处理器,其中包含了一些"u-"开头的自定义变量 3 | * 使用的时候,请将下面的一行复制到您的uniapp项目根目录的uni.scss中即可 4 | * uView自定义的css类名和scss变量,均以"u-"开头,不会造成冲突,请放心使用 5 | */ 6 | @import 'uview-ui/theme.scss'; 7 | 8 | -------------------------------------------------------------------------------- /client_mp/uview-ui/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 www.uviewui.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /client_mp/uview-ui/components/u-cell-group/u-cell-group.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 51 | 52 | 71 | -------------------------------------------------------------------------------- /client_mp/uview-ui/components/u-full-screen/u-full-screen.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 38 | 39 | 53 | -------------------------------------------------------------------------------- /client_mp/uview-ui/components/u-gap/u-gap.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 51 | 52 | 55 | -------------------------------------------------------------------------------- /client_mp/uview-ui/components/u-loading-page/u-loading-page.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | 26 | -------------------------------------------------------------------------------- /client_mp/uview-ui/components/u-parse/libs/handler.wxs: -------------------------------------------------------------------------------- 1 | var inline = { 2 | abbr: 1, 3 | b: 1, 4 | big: 1, 5 | code: 1, 6 | del: 1, 7 | em: 1, 8 | i: 1, 9 | ins: 1, 10 | label: 1, 11 | q: 1, 12 | small: 1, 13 | span: 1, 14 | strong: 1, 15 | sub: 1, 16 | sup: 1 17 | } 18 | module.exports = { 19 | use: function(item) { 20 | return !item.c && !inline[item.name] && (item.attrs.style || '').indexOf('display:inline') == -1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /client_mp/uview-ui/components/u-td/u-td.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 51 | 52 | 67 | -------------------------------------------------------------------------------- /client_mp/uview-ui/components/u-th/u-th.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 48 | 49 | 63 | -------------------------------------------------------------------------------- /client_mp/uview-ui/components/u-time-line/u-time-line.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | 24 | 44 | -------------------------------------------------------------------------------- /client_mp/uview-ui/components/u-tr/u-tr.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | 26 | -------------------------------------------------------------------------------- /client_mp/uview-ui/index.scss: -------------------------------------------------------------------------------- 1 | // 引入公共基础类 2 | @import "./libs/css/common.scss"; 3 | @import "./libs/css/color.scss"; 4 | 5 | // 非nvue的样式 6 | /* #ifndef APP-NVUE */ 7 | @import "./libs/css/style.vue.scss"; 8 | /* #endif */ 9 | 10 | // nvue的特有样式 11 | /* #ifdef APP-NVUE */ 12 | @import "./libs/css/style.nvue.scss"; 13 | /* #endif */ 14 | 15 | // 小程序特有的样式 16 | /* #ifdef MP */ 17 | @import "./libs/css/style.mp.scss"; 18 | /* #endif */ 19 | 20 | // H5特有的样式 21 | /* #ifdef H5 */ 22 | @import "./libs/css/style.h5.scss"; 23 | /* #endif */ -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/config/config.js: -------------------------------------------------------------------------------- 1 | // 此版本发布于2020-11-19 2 | let version = '1.8.2'; 3 | 4 | export default { 5 | v: version, 6 | version: version, 7 | // 主题名称 8 | type: [ 9 | 'primary', 10 | 'success', 11 | 'info', 12 | 'error', 13 | 'warning' 14 | ] 15 | } -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/config/zIndex.js: -------------------------------------------------------------------------------- 1 | // uniapp在H5中各API的z-index值如下: 2 | /** 3 | * actionsheet: 999 4 | * modal: 999 5 | * navigate: 998 6 | * tabbar: 998 7 | * toast: 999 8 | */ 9 | 10 | export default { 11 | toast: 10090, 12 | noNetwork: 10080, 13 | // popup包含popup,actionsheet,keyboard,picker的值 14 | popup: 10075, 15 | mask: 10070, 16 | navbar: 980, 17 | topTips: 975, 18 | sticky: 970, 19 | indexListSticky: 965, 20 | } -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/css/style.components.scss: -------------------------------------------------------------------------------- 1 | // 定义混入指令,用于在非nvue环境下的flex定义,因为nvue没有display属性,会报错 2 | @mixin vue-flex($direction: row) { 3 | /* #ifndef APP-NVUE */ 4 | display: flex; 5 | flex-direction: $direction; 6 | /* #endif */ 7 | } -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/css/style.h5.scss: -------------------------------------------------------------------------------- 1 | /* H5的时候,隐藏滚动条 */ 2 | ::-webkit-scrollbar { 3 | display: none; 4 | width: 0 !important; 5 | height: 0 !important; 6 | -webkit-appearance: none; 7 | background: transparent; 8 | } 9 | -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/css/style.mp.scss: -------------------------------------------------------------------------------- 1 | /* start--微信小程序编译后页面有组件名的元素,特别处理--start */ 2 | /* #ifdef MP-WEIXIN || MP-QQ */ 3 | u-td, u-th { 4 | flex: 1; 5 | align-self: stretch; 6 | } 7 | 8 | .u-td { 9 | height: 100%; 10 | } 11 | 12 | u-icon { 13 | display: inline-flex; 14 | align-items: center; 15 | } 16 | 17 | // 各家小程序宫格组件外层设置为100%,避免受到父元素display: flex;的影响 18 | u-grid { 19 | width: 100%; 20 | flex: 0 0 100%; 21 | } 22 | 23 | // 避免小程序线条组件因为父组件display: flex;而失效 24 | u-line { 25 | flex: 1; 26 | } 27 | 28 | u-switch { 29 | display: inline-flex; 30 | align-items: center; 31 | } 32 | 33 | u-dropdown { 34 | flex: 1; 35 | } 36 | /* #endif */ 37 | /* end-微信小程序编译后页面有组件名的元素,特别处理--end */ 38 | 39 | 40 | /* #ifdef MP-QQ || MP-TOUTIAO */ 41 | // 需要做这一切额外的兼容,都是因为TX的无能 42 | u-icon { 43 | line-height: 0; 44 | } 45 | /* #endif */ 46 | 47 | /* start--头条小程序编译后页面有组件名的元素,特别处理--start */ 48 | // 由于头条小程序不支持直接组件名形式写样式,目前只能在写组件的时候给组件加上对应的类名 49 | /* #ifdef MP-TOUTIAO */ 50 | .u-td, .u-th, .u-tr { 51 | flex: 1; 52 | align-self: stretch; 53 | } 54 | 55 | .u-row, .u-col { 56 | flex: 1; 57 | align-self: stretch; 58 | } 59 | 60 | // 避免小程序线条组件因为父组件display: flex;而失效 61 | .u-line { 62 | flex: 1; 63 | } 64 | 65 | .u-dropdown { 66 | flex: 1; 67 | } 68 | /* #endif */ 69 | /* end-头条小程序编译后页面有组件名的元素,特别处理--end */ 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/css/style.nvue.scss: -------------------------------------------------------------------------------- 1 | .nvue { 2 | font-size: 24rpx; 3 | } -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/function/$parent.js: -------------------------------------------------------------------------------- 1 | // 获取父组件的参数,因为支付宝小程序不支持provide/inject的写法 2 | // this.$parent在非H5中,可以准确获取到父组件,但是在H5中,需要多次this.$parent.$parent.xxx 3 | // 这里默认值等于undefined有它的含义,因为最顶层元素(组件)的$parent就是undefined,意味着不传name 4 | // 值(默认为undefined),就是查找最顶层的$parent 5 | export default function $parent(name = undefined) { 6 | let parent = this.$parent; 7 | // 通过while历遍,这里主要是为了H5需要多层解析的问题 8 | while (parent) { 9 | // 父组件 10 | if (parent.$options && parent.$options.name !== name) { 11 | // 如果组件的name不相等,继续上一级寻找 12 | parent = parent.$parent; 13 | } else { 14 | return parent; 15 | } 16 | } 17 | return false; 18 | } -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/function/addUnit.js: -------------------------------------------------------------------------------- 1 | import validation from './test.js'; 2 | 3 | // 添加单位,如果有rpx,%,px等单位结尾或者值为auto,直接返回,否则加上rpx单位结尾 4 | export default function addUnit(value = 'auto', unit = 'rpx') { 5 | value = String(value); 6 | // 用uView内置验证规则中的number判断是否为数值 7 | return validation.number(value) ? `${value}${unit}` : value; 8 | } -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/function/bem.js: -------------------------------------------------------------------------------- 1 | function bem(name, conf) { 2 | 3 | } 4 | 5 | module.exports.bem = bem; 6 | -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/function/color.js: -------------------------------------------------------------------------------- 1 | // 为了让用户能够自定义主题,会逐步弃用此文件,各颜色通过css提供 2 | // 为了给某些特殊场景使用和向后兼容,无需删除此文件(2020-06-20) 3 | let color = { 4 | primary: "#2979ff", 5 | primaryDark: "#2b85e4", 6 | primaryDisabled: "#a0cfff", 7 | primaryLight: "#ecf5ff", 8 | bgColor: "#f3f4f6", 9 | 10 | info: "#909399", 11 | infoDark: "#82848a", 12 | infoDisabled: "#c8c9cc", 13 | infoLight: "#f4f4f5", 14 | 15 | warning: "#ff9900", 16 | warningDark: "#f29100", 17 | warningDisabled: "#fcbd71", 18 | warningLight: "#fdf6ec", 19 | 20 | error: "#fa3534", 21 | errorDark: "#dd6161", 22 | errorDisabled: "#fab6b6", 23 | errorLight: "#fef0f0", 24 | 25 | success: "#19be6b", 26 | successDark: "#18b566", 27 | successDisabled: "#71d5a1", 28 | successLight: "#dbf1e1", 29 | 30 | mainColor: "#303133", 31 | contentColor: "#606266", 32 | tipsColor: "#909399", 33 | lightColor: "#c0c4cc", 34 | borderColor: "#e4e7ed" 35 | } 36 | 37 | export default color; -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/function/debounce.js: -------------------------------------------------------------------------------- 1 | let timeout = null; 2 | 3 | /** 4 | * 防抖原理:一定时间内,只有最后一次操作,再过wait毫秒后才执行函数 5 | * 6 | * @param {Function} func 要执行的回调函数 7 | * @param {Number} wait 延时的时间 8 | * @param {Boolean} immediate 是否立即执行 9 | * @return null 10 | */ 11 | function debounce(func, wait = 500, immediate = false) { 12 | // 清除定时器 13 | if (timeout !== null) clearTimeout(timeout); 14 | // 立即执行,此类情况一般用不到 15 | if (immediate) { 16 | var callNow = !timeout; 17 | timeout = setTimeout(function() { 18 | timeout = null; 19 | }, wait); 20 | if (callNow) typeof func === 'function' && func(); 21 | } else { 22 | // 设置定时器,当最后一次操作后,timeout不会再被清除,所以在延时wait毫秒后执行func回调方法 23 | timeout = setTimeout(function() { 24 | typeof func === 'function' && func(); 25 | }, wait); 26 | } 27 | } 28 | 29 | export default debounce 30 | -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/function/deepClone.js: -------------------------------------------------------------------------------- 1 | // 判断arr是否为一个数组,返回一个bool值 2 | function isArray (arr) { 3 | return Object.prototype.toString.call(arr) === '[object Array]'; 4 | } 5 | 6 | // 深度克隆 7 | function deepClone (obj) { 8 | // 对常见的“非”值,直接返回原来值 9 | if([null, undefined, NaN, false].includes(obj)) return obj; 10 | if(typeof obj !== "object" && typeof obj !== 'function') { 11 | //原始类型直接返回 12 | return obj; 13 | } 14 | var o = isArray(obj) ? [] : {}; 15 | for(let i in obj) { 16 | if(obj.hasOwnProperty(i)){ 17 | o[i] = typeof obj[i] === "object" ? deepClone(obj[i]) : obj[i]; 18 | } 19 | } 20 | return o; 21 | } 22 | 23 | export default deepClone; 24 | -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/function/deepMerge.js: -------------------------------------------------------------------------------- 1 | import deepClone from "./deepClone"; 2 | 3 | // JS对象深度合并 4 | function deepMerge(target = {}, source = {}) { 5 | target = deepClone(target); 6 | if (typeof target !== 'object' || typeof source !== 'object') return false; 7 | for (var prop in source) { 8 | if (!source.hasOwnProperty(prop)) continue; 9 | if (prop in target) { 10 | if (typeof target[prop] !== 'object') { 11 | target[prop] = source[prop]; 12 | } else { 13 | if (typeof source[prop] !== 'object') { 14 | target[prop] = source[prop]; 15 | } else { 16 | if (target[prop].concat && source[prop].concat) { 17 | target[prop] = target[prop].concat(source[prop]); 18 | } else { 19 | target[prop] = deepMerge(target[prop], source[prop]); 20 | } 21 | } 22 | } 23 | } else { 24 | target[prop] = source[prop]; 25 | } 26 | } 27 | return target; 28 | } 29 | 30 | export default deepMerge; -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/function/getParent.js: -------------------------------------------------------------------------------- 1 | // 获取父组件的参数,因为支付宝小程序不支持provide/inject的写法 2 | // this.$parent在非H5中,可以准确获取到父组件,但是在H5中,需要多次this.$parent.$parent.xxx 3 | export default function getParent(name, keys) { 4 | let parent = this.$parent; 5 | // 通过while历遍,这里主要是为了H5需要多层解析的问题 6 | while (parent) { 7 | // 父组件 8 | if (parent.$options.name !== name) { 9 | // 如果组件的name不相等,继续上一级寻找 10 | parent = parent.$parent; 11 | } else { 12 | let data = {}; 13 | // 判断keys是否数组,如果传过来的是一个数组,那么直接使用数组元素值当做键值去父组件寻找 14 | if(Array.isArray(keys)) { 15 | keys.map(val => { 16 | data[val] = parent[val] ? parent[val] : ''; 17 | }) 18 | } else { 19 | // 历遍传过来的对象参数 20 | for(let i in keys) { 21 | // 如果子组件有此值则用,无此值则用父组件的值 22 | // 判断是否空数组,如果是,则用父组件的值,否则用子组件的值 23 | if(Array.isArray(keys[i])) { 24 | if(keys[i].length) { 25 | data[i] = keys[i]; 26 | } else { 27 | data[i] = parent[i]; 28 | } 29 | } else if(keys[i].constructor === Object) { 30 | // 判断是否对象,如果是对象,且有属性,那么使用子组件的值,否则使用父组件的值 31 | if(Object.keys(keys[i]).length) { 32 | data[i] = keys[i]; 33 | } else { 34 | data[i] = parent[i]; 35 | } 36 | } else { 37 | // 只要子组件有传值,即使是false值,也是“传值”了,也需要覆盖父组件的同名参数 38 | data[i] = (keys[i] || keys[i] === false) ? keys[i] : parent[i]; 39 | } 40 | } 41 | } 42 | return data; 43 | } 44 | } 45 | 46 | return {}; 47 | } -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/function/guid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 本算法来源于简书开源代码,详见:https://www.jianshu.com/p/fdbf293d0a85 3 | * 全局唯一标识符(uuid,Globally Unique Identifier),也称作 uuid(Universally Unique IDentifier) 4 | * 一般用于多个组件之间,给它一个唯一的标识符,或者v-for循环的时候,如果使用数组的index可能会导致更新列表出现问题 5 | * 最可能的情况是左滑删除item或者对某条信息流"不喜欢"并去掉它的时候,会导致组件内的数据可能出现错乱 6 | * v-for的时候,推荐使用后端返回的id而不是循环的index 7 | * @param {Number} len uuid的长度 8 | * @param {Boolean} firstU 将返回的首字母置为"u" 9 | * @param {Nubmer} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制 10 | */ 11 | function guid(len = 32, firstU = true, radix = null) { 12 | let chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); 13 | let uuid = []; 14 | radix = radix || chars.length; 15 | 16 | if (len) { 17 | // 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位 18 | for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]; 19 | } else { 20 | let r; 21 | // rfc4122标准要求返回的uuid中,某些位为固定的字符 22 | uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; 23 | uuid[14] = '4'; 24 | 25 | for (let i = 0; i < 36; i++) { 26 | if (!uuid[i]) { 27 | r = 0 | Math.random() * 16; 28 | uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; 29 | } 30 | } 31 | } 32 | // 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class 33 | if (firstU) { 34 | uuid.shift(); 35 | return 'u' + uuid.join(''); 36 | } else { 37 | return uuid.join(''); 38 | } 39 | } 40 | 41 | export default guid; 42 | -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/function/queryParams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 对象转url参数 3 | * @param {*} data,对象 4 | * @param {*} isPrefix,是否自动加上"?" 5 | */ 6 | function queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') { 7 | let prefix = isPrefix ? '?' : '' 8 | let _result = [] 9 | if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets'; 10 | for (let key in data) { 11 | let value = data[key] 12 | // 去掉为空的参数 13 | if (['', undefined, null].indexOf(value) >= 0) { 14 | continue; 15 | } 16 | // 如果值为数组,另行处理 17 | if (value.constructor === Array) { 18 | // e.g. {ids: [1, 2, 3]} 19 | switch (arrayFormat) { 20 | case 'indices': 21 | // 结果: ids[0]=1&ids[1]=2&ids[2]=3 22 | for (let i = 0; i < value.length; i++) { 23 | _result.push(key + '[' + i + ']=' + value[i]) 24 | } 25 | break; 26 | case 'brackets': 27 | // 结果: ids[]=1&ids[]=2&ids[]=3 28 | value.forEach(_value => { 29 | _result.push(key + '[]=' + _value) 30 | }) 31 | break; 32 | case 'repeat': 33 | // 结果: ids=1&ids=2&ids=3 34 | value.forEach(_value => { 35 | _result.push(key + '=' + _value) 36 | }) 37 | break; 38 | case 'comma': 39 | // 结果: ids=1,2,3 40 | let commaStr = ""; 41 | value.forEach(_value => { 42 | commaStr += (commaStr ? "," : "") + _value; 43 | }) 44 | _result.push(key + '=' + commaStr) 45 | break; 46 | default: 47 | value.forEach(_value => { 48 | _result.push(key + '[]=' + _value) 49 | }) 50 | } 51 | } else { 52 | _result.push(key + '=' + value) 53 | } 54 | } 55 | return _result.length ? prefix + _result.join('&') : '' 56 | } 57 | 58 | export default queryParams; 59 | -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/function/random.js: -------------------------------------------------------------------------------- 1 | function random(min, max) { 2 | if (min >= 0 && max > 0 && max >= min) { 3 | let gab = max - min + 1; 4 | return Math.floor(Math.random() * gab + min); 5 | } else { 6 | return 0; 7 | } 8 | } 9 | 10 | export default random; 11 | -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/function/randomArray.js: -------------------------------------------------------------------------------- 1 | // 打乱数组 2 | function randomArray(array = []) { 3 | // 原理是sort排序,Math.random()产生0<= x < 1之间的数,会导致x-0.05大于或者小于0 4 | return array.sort(() => Math.random() - 0.5); 5 | } 6 | 7 | export default randomArray 8 | -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/function/sys.js: -------------------------------------------------------------------------------- 1 | export function os() { 2 | return uni.getSystemInfoSync().platform; 3 | }; 4 | 5 | export function sys() { 6 | return uni.getSystemInfoSync(); 7 | } 8 | 9 | 10 | -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/function/throttle.js: -------------------------------------------------------------------------------- 1 | let timer, flag; 2 | /** 3 | * 节流原理:在一定时间内,只能触发一次 4 | * 5 | * @param {Function} func 要执行的回调函数 6 | * @param {Number} wait 延时的时间 7 | * @param {Boolean} immediate 是否立即执行 8 | * @return null 9 | */ 10 | function throttle(func, wait = 500, immediate = true) { 11 | if (immediate) { 12 | if (!flag) { 13 | flag = true; 14 | // 如果是立即执行,则在wait毫秒内开始时执行 15 | typeof func === 'function' && func(); 16 | timer = setTimeout(() => { 17 | flag = false; 18 | }, wait); 19 | } 20 | } else { 21 | if (!flag) { 22 | flag = true 23 | // 如果是非立即执行,则在wait毫秒内的结束处执行 24 | timer = setTimeout(() => { 25 | flag = false 26 | typeof func === 'function' && func(); 27 | }, wait); 28 | } 29 | 30 | } 31 | }; 32 | export default throttle 33 | -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/function/timeFormat.js: -------------------------------------------------------------------------------- 1 | // padStart 的 polyfill,因为某些机型或情况,还无法支持es7的padStart,比如电脑版的微信小程序 2 | // 所以这里做一个兼容polyfill的兼容处理 3 | if (!String.prototype.padStart) { 4 | // 为了方便表示这里 fillString 用了ES6 的默认参数,不影响理解 5 | String.prototype.padStart = function(maxLength, fillString = ' ') { 6 | if (Object.prototype.toString.call(fillString) !== "[object String]") throw new TypeError( 7 | 'fillString must be String') 8 | let str = this 9 | // 返回 String(str) 这里是为了使返回的值是字符串字面量,在控制台中更符合直觉 10 | if (str.length >= maxLength) return String(str) 11 | 12 | let fillLength = maxLength - str.length, 13 | times = Math.ceil(fillLength / fillString.length) 14 | while (times >>= 1) { 15 | fillString += fillString 16 | if (times === 1) { 17 | fillString += fillString 18 | } 19 | } 20 | return fillString.slice(0, fillLength) + str; 21 | } 22 | } 23 | 24 | // 其他更多是格式化有如下: 25 | // yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合 26 | function timeFormat(dateTime = null, fmt = 'yyyy-mm-dd') { 27 | // 如果为null,则格式化当前时间 28 | if (!dateTime) dateTime = Number(new Date()); 29 | // 如果dateTime长度为10或者13,则为秒和毫秒的时间戳,如果超过13位,则为其他的时间格式 30 | if (dateTime.toString().length == 10) dateTime *= 1000; 31 | let date = new Date(Number(dateTime)); 32 | let ret; 33 | let opt = { 34 | "y+": date.getFullYear().toString(), // 年 35 | "m+": (date.getMonth() + 1).toString(), // 月 36 | "d+": date.getDate().toString(), // 日 37 | "h+": date.getHours().toString(), // 时 38 | "M+": date.getMinutes().toString(), // 分 39 | "s+": date.getSeconds().toString() // 秒 40 | // 有其他格式化字符需求可以继续添加,必须转化成字符串 41 | }; 42 | for (let k in opt) { 43 | ret = new RegExp("(" + k + ")").exec(fmt); 44 | if (ret) { 45 | fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0"))) 46 | }; 47 | }; 48 | return fmt; 49 | } 50 | 51 | export default timeFormat 52 | -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/function/timeFrom.js: -------------------------------------------------------------------------------- 1 | import timeFormat from '../../libs/function/timeFormat.js'; 2 | 3 | /** 4 | * 时间戳转为多久之前 5 | * @param String timestamp 时间戳 6 | * @param String | Boolean format 如果为时间格式字符串,超出一定时间范围,返回固定的时间格式; 7 | * 如果为布尔值false,无论什么时间,都返回多久以前的格式 8 | */ 9 | function timeFrom(dateTime = null, format = 'yyyy-mm-dd') { 10 | // 如果为null,则格式化当前时间 11 | if (!dateTime) dateTime = Number(new Date()); 12 | // 如果dateTime长度为10或者13,则为秒和毫秒的时间戳,如果超过13位,则为其他的时间格式 13 | if (dateTime.toString().length == 10) dateTime *= 1000; 14 | let timestamp = + new Date(Number(dateTime)); 15 | 16 | let timer = (Number(new Date()) - timestamp) / 1000; 17 | // 如果小于5分钟,则返回"刚刚",其他以此类推 18 | let tips = ''; 19 | switch (true) { 20 | case timer < 300: 21 | tips = '刚刚'; 22 | break; 23 | case timer >= 300 && timer < 3600: 24 | tips = parseInt(timer / 60) + '分钟前'; 25 | break; 26 | case timer >= 3600 && timer < 86400: 27 | tips = parseInt(timer / 3600) + '小时前'; 28 | break; 29 | case timer >= 86400 && timer < 2592000: 30 | tips = parseInt(timer / 86400) + '天前'; 31 | break; 32 | default: 33 | // 如果format为false,则无论什么时间戳,都显示xx之前 34 | if(format === false) { 35 | if(timer >= 2592000 && timer < 365 * 86400) { 36 | tips = parseInt(timer / (86400 * 30)) + '个月前'; 37 | } else { 38 | tips = parseInt(timer / (86400 * 365)) + '年前'; 39 | } 40 | } else { 41 | tips = timeFormat(timestamp, format); 42 | } 43 | } 44 | return tips; 45 | } 46 | 47 | export default timeFrom; 48 | -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/function/toast.js: -------------------------------------------------------------------------------- 1 | function toast(title, duration = 1500) { 2 | uni.showToast({ 3 | title: title, 4 | icon: 'none', 5 | duration: duration 6 | }) 7 | } 8 | 9 | export default toast 10 | -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/function/trim.js: -------------------------------------------------------------------------------- 1 | function trim(str, pos = 'both') { 2 | if (pos == 'both') { 3 | return str.replace(/^\s+|\s+$/g, ""); 4 | } else if (pos == "left") { 5 | return str.replace(/^\s*/, ''); 6 | } else if (pos == 'right') { 7 | return str.replace(/(\s*$)/g, ""); 8 | } else if (pos == 'all') { 9 | return str.replace(/\s+/g, ""); 10 | } else { 11 | return str; 12 | } 13 | } 14 | 15 | export default trim 16 | -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/function/type2icon.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 根据主题type值,获取对应的图标 3 | * @param String type 主题名称,primary|info|error|warning|success 4 | * @param String fill 是否使用fill填充实体的图标 5 | */ 6 | function type2icon(type = 'success', fill = false) { 7 | // 如果非预置值,默认为success 8 | if (['primary', 'info', 'error', 'warning', 'success'].indexOf(type) == -1) type = 'success'; 9 | let iconName = ''; 10 | // 目前(2019-12-12),info和primary使用同一个图标 11 | switch (type) { 12 | case 'primary': 13 | iconName = 'info-circle'; 14 | break; 15 | case 'info': 16 | iconName = 'info-circle'; 17 | break; 18 | case 'error': 19 | iconName = 'close-circle'; 20 | break; 21 | case 'warning': 22 | iconName = 'error-circle'; 23 | break; 24 | case 'success': 25 | iconName = 'checkmark-circle'; 26 | break; 27 | default: 28 | iconName = 'checkmark-circle'; 29 | } 30 | // 是否是实体类型,加上-fill,在icon组件库中,实体的类名是后面加-fill的 31 | if (fill) iconName += '-fill'; 32 | return iconName; 33 | } 34 | 35 | export default type2icon 36 | -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/mixin/mixin.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | data() { 3 | return {} 4 | }, 5 | onLoad() { 6 | // getRect挂载到$u上,因为这方法需要使用in(this),所以无法把它独立成一个单独的文件导出 7 | this.$u.getRect = this.$uGetRect 8 | }, 9 | methods: { 10 | // 查询节点信息 11 | // 目前此方法在支付宝小程序中无法获取组件跟接点的尺寸,为支付宝的bug(2020-07-21) 12 | // 解决办法为在组件根部再套一个没有任何作用的view元素 13 | $uGetRect(selector, all) { 14 | return new Promise(resolve => { 15 | uni.createSelectorQuery(). 16 | in(this)[all ? 'selectAll' : 'select'](selector) 17 | .boundingClientRect(rect => { 18 | if (all && Array.isArray(rect) && rect.length) { 19 | resolve(rect) 20 | } 21 | if (!all && rect) { 22 | resolve(rect) 23 | } 24 | }) 25 | .exec() 26 | }) 27 | }, 28 | getParentData(parentName = '') { 29 | // 避免在created中去定义parent变量 30 | if(!this.parent) this.parent = false; 31 | // 这里的本质原理是,通过获取父组件实例(也即u-radio-group的this) 32 | // 将父组件this中对应的参数,赋值给本组件(u-radio的this)的parentData对象中对应的属性 33 | // 之所以需要这么做,是因为所有端中,头条小程序不支持通过this.parent.xxx去监听父组件参数的变化 34 | this.parent = this.$u.$parent.call(this, parentName); 35 | if(this.parent) { 36 | // 历遍parentData中的属性,将parent中的同名属性赋值给parentData 37 | Object.keys(this.parentData).map(key => { 38 | this.parentData[key] = this.parent[key]; 39 | }); 40 | } 41 | }, 42 | // 阻止事件冒泡 43 | preventEvent(e) { 44 | e && e.stopPropagation && e.stopPropagation() 45 | } 46 | }, 47 | onReachBottom() { 48 | uni.$emit('uOnReachBottom') 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/mixin/mpShare.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | onLoad() { 3 | // 设置默认的转发参数 4 | this.$u.mpShare = { 5 | title: '', // 默认为小程序名称 6 | path: '', // 默认为当前页面路径 7 | imageUrl: '' // 默认为当前页面的截图 8 | } 9 | }, 10 | onShareAppMessage() { 11 | return this.$u.mpShare 12 | }, 13 | // #ifdef MP-WEIXIN 14 | onShareTimeline() { 15 | return this.$u.mpShare 16 | } 17 | // #endif 18 | } 19 | -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/store/index.js: -------------------------------------------------------------------------------- 1 | // 暂时不用vuex模块方式实现,将该方法直接放入到/store/index.js中 2 | const module = { 3 | actions: { 4 | $uStore({rootState}, params) { 5 | let nameArr = params.name.split('.'); 6 | if(nameArr.length >= 2) { 7 | let obj = rootState[nameArr[0]]; 8 | for(let i = 1; i < nameArr.length - 1; i ++) { 9 | obj = obj[nameArr[i]]; 10 | } 11 | obj[nameArr[nameArr.length - 1]] = params.value; 12 | } else { 13 | rootState[params.name] = params.value; 14 | } 15 | } 16 | } 17 | } 18 | 19 | export default module -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/util/emitter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 递归使用 call 方式this指向 3 | * @param componentName // 需要找的组件的名称 4 | * @param eventName // 事件名称 5 | * @param params // 需要传递的参数 6 | */ 7 | function broadcast(componentName, eventName, params) { 8 | // 循环子节点找到名称一样的子节点 否则 递归 当前子节点 9 | this.$children.map(child=>{ 10 | if (componentName===child.$options.name) { 11 | child.$emit.apply(child,[eventName].concat(params)) 12 | }else { 13 | broadcast.apply(child,[componentName,eventName].concat(params)) 14 | } 15 | }) 16 | } 17 | export default { 18 | methods: { 19 | /** 20 | * 派发 (向上查找) (一个) 21 | * @param componentName // 需要找的组件的名称 22 | * @param eventName // 事件名称 23 | * @param params // 需要传递的参数 24 | */ 25 | dispatch(componentName, eventName, params) { 26 | let parent = this.$parent || this.$root;//$parent 找到最近的父节点 $root 根节点 27 | let name = parent.$options.name; // 获取当前组件实例的name 28 | // 如果当前有节点 && 当前没名称 且 当前名称等于需要传进来的名称的时候就去查找当前的节点 29 | // 循环出当前名称的一样的组件实例 30 | while (parent && (!name||name!==componentName)) { 31 | parent = parent.$parent; 32 | if (parent) { 33 | name = parent.$options.name; 34 | } 35 | } 36 | // 有节点表示当前找到了name一样的实例 37 | if (parent) { 38 | parent.$emit.apply(parent,[eventName].concat(params)) 39 | } 40 | }, 41 | /** 42 | * 广播 (向下查找) (广播多个) 43 | * @param componentName // 需要找的组件的名称 44 | * @param eventName // 事件名称 45 | * @param params // 需要传递的参数 46 | */ 47 | broadcast(componentName, eventName, params) { 48 | broadcast.call(this,componentName, eventName, params) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /client_mp/uview-ui/libs/util/province.js: -------------------------------------------------------------------------------- 1 | var provinceData=[{"label":"北京市","value":"11"},{"label":"天津市","value":"12"},{"label":"河北省","value":"13"},{"label":"山西省","value":"14"},{"label":"内蒙古自治区","value":"15"},{"label":"辽宁省","value":"21"},{"label":"吉林省","value":"22"},{"label":"黑龙江省","value":"23"},{"label":"上海市","value":"31"},{"label":"江苏省","value":"32"},{"label":"浙江省","value":"33"},{"label":"安徽省","value":"34"},{"label":"福建省","value":"35"},{"label":"江西省","value":"36"},{"label":"山东省","value":"37"},{"label":"河南省","value":"41"},{"label":"湖北省","value":"42"},{"label":"湖南省","value":"43"},{"label":"广东省","value":"44"},{"label":"广西壮族自治区","value":"45"},{"label":"海南省","value":"46"},{"label":"重庆市","value":"50"},{"label":"四川省","value":"51"},{"label":"贵州省","value":"52"},{"label":"云南省","value":"53"},{"label":"西藏自治区","value":"54"},{"label":"陕西省","value":"61"},{"label":"甘肃省","value":"62"},{"label":"青海省","value":"63"},{"label":"宁夏回族自治区","value":"64"},{"label":"新疆维吾尔自治区","value":"65"},{"label":"台湾","value":"66"},{"label":"香港","value":"67"},{"label":"澳门","value":"68"}];export default provinceData; -------------------------------------------------------------------------------- /client_mp/uview-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uview-ui", 3 | "version": "1.8.2", 4 | "description": "uView UI,是uni-app生态优秀的UI框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水", 5 | "main": "index.js", 6 | "keywords": [ 7 | "uview", 8 | "uView", 9 | "uni-app", 10 | "uni-app ui", 11 | "uniapp", 12 | "uviewui", 13 | "uview ui", 14 | "uviewUI", 15 | "uViewui", 16 | "uViewUI", 17 | "uView UI", 18 | "uni ui", 19 | "uni UI", 20 | "uniapp ui", 21 | "ui", 22 | "UI框架", 23 | "uniapp ui框架", 24 | "uniapp UI" 25 | ], 26 | "scripts": { 27 | "test": "echo \"Error: no test specified\" && exit 1" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "" 32 | }, 33 | "devDependencies": { 34 | "node-sass": "^4.14.0", 35 | "sass-loader": "^8.0.2" 36 | }, 37 | "author": "uView", 38 | "license": "MIT" 39 | } 40 | -------------------------------------------------------------------------------- /client_mp/uview-ui/theme.scss: -------------------------------------------------------------------------------- 1 | // 此文件为uView的主题变量,这些变量目前只能通过uni.scss引入才有效,另外由于 2 | // uni.scss中引入的样式会同时混入到全局样式文件和单独每一个页面的样式中,造成微信程序包太大, 3 | // 故uni.scss只建议放scss变量名相关样式,其他的样式可以通过main.js或者App.vue引入 4 | 5 | $u-main-color: #303133; 6 | $u-content-color: #606266; 7 | $u-tips-color: #909399; 8 | $u-light-color: #c0c4cc; 9 | $u-border-color: #e4e7ed; 10 | $u-bg-color: #f3f4f6; 11 | 12 | $u-type-primary: #2979ff; 13 | $u-type-primary-light: #ecf5ff; 14 | $u-type-primary-disabled: #a0cfff; 15 | $u-type-primary-dark: #2b85e4; 16 | 17 | $u-type-warning: #ff9900; 18 | $u-type-warning-disabled: #fcbd71; 19 | $u-type-warning-dark: #f29100; 20 | $u-type-warning-light: #fdf6ec; 21 | 22 | $u-type-success: #19be6b; 23 | $u-type-success-disabled: #71d5a1; 24 | $u-type-success-dark: #18b566; 25 | $u-type-success-light: #dbf1e1; 26 | 27 | $u-type-error: #fa3534; 28 | $u-type-error-disabled: #fab6b6; 29 | $u-type-error-dark: #dd6161; 30 | $u-type-error-light: #fef0f0; 31 | 32 | $u-type-info: #909399; 33 | $u-type-info-disabled: #c8c9cc; 34 | $u-type-info-dark: #82848a; 35 | $u-type-info-light: #f4f4f5; 36 | 37 | $u-form-item-height: 70rpx; 38 | $u-form-item-border-color: #dcdfe6; 39 | -------------------------------------------------------------------------------- /client_mp/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // 配置路径别名 3 | configureWebpack: { 4 | devServer: { 5 | // 调试时允许内网穿透,让外网的人访问到本地调试的H5页面 6 | disableHostCheck: true 7 | } 8 | }, 9 | //productionSourceMap: false, 10 | } 11 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | backend: 4 | build: ./server 5 | ports: 6 | - "8000:80" 7 | environment: 8 | # 生产的话把DJANGO_ENV这个环境变量删了 执行docker-compose build backend 重新构建下镜像 9 | - DJANGO_ENV=dev 10 | volumes: 11 | - ./server:/code 12 | links: 13 | - redis 14 | frontend: 15 | build: 16 | context: ./client 17 | # 生产用这个 18 | # dockerfile: Dockerfile 19 | # 开发的话用这个 20 | dockerfile: Dockerfile_dev 21 | ports: 22 | - "8012:80" 23 | redis: 24 | image: redis 25 | command: redis-server --appendonly yes -------------------------------------------------------------------------------- /img/dict.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/img/dict.png -------------------------------------------------------------------------------- /img/docs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/img/docs.png -------------------------------------------------------------------------------- /img/task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/img/task.png -------------------------------------------------------------------------------- /img/ticket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/img/ticket.png -------------------------------------------------------------------------------- /img/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/img/user.png -------------------------------------------------------------------------------- /img/wechat_group.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/img/wechat_group.jpg -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | .vs/ 3 | venv/ 4 | __pycache__/ 5 | *.pyc 6 | media/* 7 | vuedist/* 8 | dist/* 9 | !media/default/ 10 | celerybeat.pid 11 | celerybeat-schedule.bak 12 | celerybeat-schedule.dat 13 | celerybeat-schedule.dir 14 | db.sqlite3 15 | server/conf.py -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8-slim 2 | WORKDIR /code 3 | ADD . . 4 | RUN sed -i -re 's/(deb|security)\.debian\.org/mirrors.aliyun.com/g' /etc/apt/sources.list &&\ 5 | apt-get update && apt-get install -y gcc libpq-dev default-libmysqlclient-dev &&\ 6 | apt-get clean && rm -rf /var/lib/apt/lists/* &&\ 7 | pip install --no-cache-dir --trusted-host mirrors.aliyun.com -i https://mirrors.aliyun.com/pypi/simple/ supervisor &&\ 8 | pip install --no-cache-dir --trusted-host mirrors.aliyun.com -i https://mirrors.aliyun.com/pypi/simple/ -r ./requirements.txt 9 | EXPOSE 80 10 | ENTRYPOINT ["/bin/bash","-C","/code/start.sh"] 11 | -------------------------------------------------------------------------------- /server/apps/crm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/server/apps/crm/__init__.py -------------------------------------------------------------------------------- /server/apps/crm/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /server/apps/crm/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CrmConfig(AppConfig): 5 | name = 'crm' 6 | -------------------------------------------------------------------------------- /server/apps/crm/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/server/apps/crm/migrations/__init__.py -------------------------------------------------------------------------------- /server/apps/crm/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /server/apps/crm/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /server/apps/crm/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /server/apps/monitor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/server/apps/monitor/__init__.py -------------------------------------------------------------------------------- /server/apps/monitor/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /server/apps/monitor/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MonitorConfig(AppConfig): 5 | name = 'apps.monitor' 6 | verbose_name = '系统监控' 7 | -------------------------------------------------------------------------------- /server/apps/monitor/middleware.py: -------------------------------------------------------------------------------- 1 | from django.utils.deprecation import MiddlewareMixin 2 | -------------------------------------------------------------------------------- /server/apps/monitor/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/server/apps/monitor/migrations/__init__.py -------------------------------------------------------------------------------- /server/apps/monitor/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /server/apps/monitor/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /server/apps/monitor/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | from rest_framework import routers 3 | from .views import ServerInfoView, LogView, LogDetailView 4 | 5 | 6 | urlpatterns = [ 7 | path('log/', LogView.as_view()), 8 | path('log//', LogDetailView.as_view()), 9 | path('server/', ServerInfoView.as_view()), 10 | ] 11 | -------------------------------------------------------------------------------- /server/apps/system/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = 'apps.system.apps.SystemConfig' -------------------------------------------------------------------------------- /server/apps/system/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from simple_history.admin import SimpleHistoryAdmin 3 | from .models import User, Organization, Role, Permission, DictType, Dict, File 4 | # Register your models here. 5 | admin.site.register(User) 6 | admin.site.register(Organization) 7 | admin.site.register(Role) 8 | admin.site.register(Permission) 9 | admin.site.register(DictType) 10 | admin.site.register(Dict, SimpleHistoryAdmin) 11 | admin.site.register(File) -------------------------------------------------------------------------------- /server/apps/system/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class SystemConfig(AppConfig): 5 | name = 'apps.system' 6 | verbose_name = '系统管理' 7 | 8 | def ready(self): 9 | import apps.system.signals -------------------------------------------------------------------------------- /server/apps/system/authentication.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.backends import ModelBackend 2 | from django.db.models import Q 3 | from django.contrib.auth import get_user_model 4 | 5 | UserModel = get_user_model() 6 | 7 | 8 | class CustomBackend(ModelBackend): 9 | def authenticate(self, request, username=None, password=None, **kwargs): 10 | if username is None: 11 | username = kwargs.get(UserModel.USERNAME_FIELD) 12 | if username is None or password is None: 13 | return 14 | try: 15 | user = UserModel._default_manager.get( 16 | Q(username=username) | Q(phone=username) | Q(email=username)) 17 | except UserModel.DoesNotExist: 18 | # Run the default password hasher once to reduce the timing 19 | # difference between an existing and a nonexistent user (#20760). 20 | UserModel().set_password(password) 21 | else: 22 | if user.check_password(password) and self.user_can_authenticate(user): 23 | return user 24 | -------------------------------------------------------------------------------- /server/apps/system/filters.py: -------------------------------------------------------------------------------- 1 | from django_filters import rest_framework as filters 2 | from .models import User 3 | 4 | 5 | class UserFilter(filters.FilterSet): 6 | class Meta: 7 | model = User 8 | fields = { 9 | 'name': ['exact', 'contains'], 10 | 'is_active': ['exact'], 11 | } 12 | -------------------------------------------------------------------------------- /server/apps/system/migrations/0002_auto_20210718_0918.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.7 on 2021-07-18 01:18 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('system', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='permission', 15 | name='method', 16 | field=models.CharField(blank=True, max_length=50, null=True, verbose_name='方法/代号'), 17 | ), 18 | migrations.AlterField( 19 | model_name='user', 20 | name='first_name', 21 | field=models.CharField(blank=True, max_length=150, verbose_name='first name'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /server/apps/system/migrations/0003_auto_20220227_1732.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.8 on 2022-02-27 09:32 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('system', '0002_auto_20210718_0918'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='role', 15 | name='depts', 16 | field=models.ManyToManyField(blank=True, related_name='roles', to='system.Organization', verbose_name='权限范围'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /server/apps/system/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/server/apps/system/migrations/__init__.py -------------------------------------------------------------------------------- /server/apps/system/mixins.py: -------------------------------------------------------------------------------- 1 | from django.db.models.query import QuerySet 2 | 3 | class CreateUpdateModelAMixin: 4 | """ 5 | 业务用基本表A用 6 | """ 7 | def perform_create(self, serializer): 8 | serializer.save(create_by = self.request.user) 9 | 10 | def perform_update(self, serializer): 11 | serializer.save(update_by = self.request.user) 12 | 13 | class CreateUpdateModelBMixin: 14 | """ 15 | 业务用基本表B用 16 | """ 17 | def perform_create(self, serializer): 18 | serializer.save(create_by = self.request.user, belong_dept=self.request.user.dept) 19 | 20 | def perform_update(self, serializer): 21 | serializer.save(update_by = self.request.user) 22 | 23 | class CreateUpdateCustomMixin: 24 | """ 25 | 整合 26 | """ 27 | def perform_create(self, serializer): 28 | if hasattr(self.queryset.model, 'belong_dept'): 29 | serializer.save(create_by = self.request.user, belong_dept=self.request.user.dept) 30 | else: 31 | serializer.save(create_by = self.request.user) 32 | def perform_update(self, serializer): 33 | serializer.save(update_by = self.request.user) 34 | 35 | class OptimizationMixin: 36 | """ 37 | 性能优化,需要在序列化器里定义setup_eager_loading,可在必要的View下继承 38 | """ 39 | def get_queryset(self): 40 | queryset = self.queryset 41 | if isinstance(queryset, QuerySet): 42 | # Ensure queryset is re-evaluated on each request. 43 | queryset = queryset.all() 44 | if hasattr(self.get_serializer_class(), 'setup_eager_loading'): 45 | queryset = self.get_serializer_class().setup_eager_loading(queryset) # 性能优化 46 | return queryset 47 | 48 | -------------------------------------------------------------------------------- /server/apps/system/signals.py: -------------------------------------------------------------------------------- 1 | from django.db.models.signals import m2m_changed 2 | from .models import Role, Permission, User 3 | from django.dispatch import receiver 4 | from django.core.cache import cache 5 | from .permission import get_permission_list 6 | 7 | # 变更用户角色时动态更新权限或者前端刷新 8 | @receiver(m2m_changed, sender=User.roles.through) 9 | def update_perms_cache_user(sender, instance, action, **kwargs): 10 | if action in ['post_remove', 'post_add']: 11 | if cache.get(instance.username+'__perms', None): 12 | get_permission_list(instance) -------------------------------------------------------------------------------- /server/apps/system/tasks.py: -------------------------------------------------------------------------------- 1 | # Create your tasks here 2 | from __future__ import absolute_import, unicode_literals 3 | 4 | from celery import shared_task 5 | 6 | 7 | @shared_task 8 | def show(): 9 | print('ok') -------------------------------------------------------------------------------- /server/apps/system/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /server/apps/system/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | from .views import TaskList, UserViewSet, OrganizationViewSet, PermissionViewSet, RoleViewSet, PositionViewSet, TestView, DictTypeViewSet, DictViewSet, PTaskViewSet 3 | from rest_framework import routers 4 | 5 | 6 | router = routers.DefaultRouter() 7 | router.register('user', UserViewSet, basename="user") 8 | router.register('organization', OrganizationViewSet, basename="organization") 9 | router.register('permission', PermissionViewSet, basename="permission") 10 | router.register('role', RoleViewSet, basename="role") 11 | router.register('position', PositionViewSet, basename="position") 12 | router.register('dicttype', DictTypeViewSet, basename="dicttype") 13 | router.register('dict', DictViewSet, basename="dict") 14 | router.register('ptask', PTaskViewSet, basename="ptask") 15 | urlpatterns = [ 16 | path('', include(router.urls)), 17 | path('task/', TaskList.as_view()), 18 | path('test/', TestView.as_view()) 19 | ] 20 | -------------------------------------------------------------------------------- /server/apps/wf/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/server/apps/wf/__init__.py -------------------------------------------------------------------------------- /server/apps/wf/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /server/apps/wf/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | class WfConfig(AppConfig): 4 | name = 'apps.wf' 5 | verbose_name = '工作流管理' 6 | 7 | 8 | -------------------------------------------------------------------------------- /server/apps/wf/filters.py: -------------------------------------------------------------------------------- 1 | from django_filters import rest_framework as filters 2 | from .models import Ticket 3 | class TicketFilterSet(filters.FilterSet): 4 | start_create = filters.DateFilter(field_name="create_time", lookup_expr='gte') 5 | end_create = filters.DateFilter(field_name="create_time", lookup_expr='lte') 6 | category = filters.ChoiceFilter(choices = Ticket.category_choices, method='filter_category') 7 | 8 | class Meta: 9 | model = Ticket 10 | fields = ['workflow', 'state', 'act_state', 'start_create', 'end_create', 'category'] 11 | 12 | def filter_category(self, queryset, name, value): 13 | user=self.request.user 14 | if value == 'owner': # 我的 15 | queryset = queryset.filter(create_by=user) 16 | elif value == 'duty': # 待办 17 | queryset = queryset.filter(participant__contains=user.id).exclude(act_state__in=[Ticket.TICKET_ACT_STATE_FINISH, Ticket.TICKET_ACT_STATE_CLOSED]) 18 | elif value == 'worked': # 处理过的 19 | queryset = queryset.filter(ticketflow_ticket__participant=user).exclude(create_by=user).order_by('-update_time').distinct() 20 | elif value == 'cc': # 抄送我的 21 | queryset = queryset.filter(ticketflow_ticket__participant_cc__contains=user.id).exclude(create_by=user).order_by('-update_time').distinct() 22 | elif value == 'all': 23 | pass 24 | else: 25 | queryset = queryset.none() 26 | return queryset -------------------------------------------------------------------------------- /server/apps/wf/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/server/apps/wf/migrations/__init__.py -------------------------------------------------------------------------------- /server/apps/wf/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /server/apps/wf/urls.py: -------------------------------------------------------------------------------- 1 | from django.db.models import base 2 | from rest_framework import urlpatterns 3 | from apps.wf.views import CustomFieldViewSet, FromCodeListView, StateViewSet, TicketFlowViewSet, TicketViewSet, TransitionViewSet, WorkflowViewSet 4 | from django.urls import path, include 5 | from rest_framework.routers import DefaultRouter 6 | 7 | router = DefaultRouter() 8 | router.register('workflow', WorkflowViewSet, basename='wf') 9 | router.register('state', StateViewSet, basename='wf_state') 10 | router.register('transition', TransitionViewSet, basename='wf_transitions') 11 | router.register('customfield', CustomFieldViewSet, basename='wf_customfield') 12 | router.register('ticket', TicketViewSet, basename='wf_ticket') 13 | router.register('ticketflow', TicketFlowViewSet, basename='wf_ticketflow') 14 | urlpatterns = [ 15 | path('participant_from_code', FromCodeListView.as_view()), 16 | path('', include(router.urls)), 17 | ] 18 | 19 | -------------------------------------------------------------------------------- /server/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/server/db.sqlite3 -------------------------------------------------------------------------------- /server/log/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /server/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /server/media/default/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/server/media/default/avatar.png -------------------------------------------------------------------------------- /server/requirements.txt: -------------------------------------------------------------------------------- 1 | celery==5.3.6 2 | Django==4.2.11 3 | django-celery-beat==2.5.0 4 | django-celery-results==2.5.1 5 | django-cors-headers==4.3.1 6 | django-filter==23.5 7 | django-simple-history==3.4.0 8 | djangorestframework==3.14.0 9 | djangorestframework-simplejwt==5.3.1 10 | drf-yasg==1.21.7 11 | psutil==5.9.6 12 | redis==5.0.1 13 | -------------------------------------------------------------------------------- /server/server/__init__.py: -------------------------------------------------------------------------------- 1 | # This will make sure the app is always imported when 2 | # Django starts so that shared_task will use this app. 3 | from .celery import app as celery_app 4 | 5 | __all__ = ('celery_app',) -------------------------------------------------------------------------------- /server/server/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for server project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /server/server/celery.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from celery import Celery 4 | 5 | # set the default Django settings module for the 'celery' program. 6 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings') 7 | 8 | app = Celery('server') 9 | 10 | # Using a string here means the worker doesn't have to serialize 11 | # the configuration object to child processes. 12 | # - namespace='CELERY' means all celery-related configuration keys 13 | # should have a `CELERY_` prefix. 14 | app.config_from_object('django.conf:settings', namespace='CELERY') 15 | 16 | # Load task modules from all registered Django app configs. 17 | app.autodiscover_tasks() 18 | 19 | 20 | @app.task(bind=True) 21 | def debug_task(self): 22 | print(f'Request: {self.request!r}') -------------------------------------------------------------------------------- /server/server/conf_e.py: -------------------------------------------------------------------------------- 1 | DEBUG = True 2 | DATABASES = { 3 | # 'default': { 4 | # 'ENGINE': 'django.db.backends.postgresql', 5 | # 'NAME': 'demo', 6 | # 'USER': 'postgres', 7 | # 'PASSWORD': '123456', 8 | # 'HOST': 'localhost', 9 | # 'PORT': '5432', 10 | # } 11 | 'default': { 12 | 'ENGINE': 'django.db.backends.sqlite3', 13 | 'NAME': 'db.sqlite3' 14 | } 15 | } -------------------------------------------------------------------------------- /server/server/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for server project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'server.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /server/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ v"$DJANGO_ENV" == 'vdev' ]; then 3 | python manage.py makemigrations system 4 | python manage.py migrate 5 | python manage.py runserver 0.0.0.0:80 6 | else 7 | python manage.py migrate 8 | python manage.py collectstatic --noinput 9 | gunicorn server.wsgi:application -w 4 -k gthread -b 0.0.0.0:80 10 | fi 11 | -------------------------------------------------------------------------------- /server/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caoqianming/django-vue-admin/92de2e9a86c434c14f291dde148bfd908e9d4c06/server/utils/__init__.py -------------------------------------------------------------------------------- /server/utils/pagination.py: -------------------------------------------------------------------------------- 1 | from rest_framework.pagination import PageNumberPagination 2 | from rest_framework.exceptions import ParseError 3 | 4 | class MyPagination(PageNumberPagination): 5 | page_size = 10 6 | page_size_query_param = 'page_size' 7 | 8 | def paginate_queryset(self, queryset, request, view=None): 9 | if request.query_params.get('pageoff', None) or request.query_params.get('page', None) == '0': 10 | if queryset.count() < 800: 11 | return None 12 | raise ParseError('单次请求数据量大,请分页获取') 13 | return super().paginate_queryset(queryset, request, view=view) 14 | 15 | class PageOrNot: 16 | def paginate_queryset(self, queryset): 17 | if (self.paginator is None): 18 | return None 19 | elif self.request.query_params.get('pageoff', None) and queryset.count()<500: 20 | return None 21 | elif self.request.query_params.get('pageoff', None) and queryset.count()>=500: 22 | raise ParseError('单次请求数据量大,请求中止') 23 | return self.paginator.paginate_queryset(queryset, self.request, view=self) 24 | -------------------------------------------------------------------------------- /server/utils/serializer.py: -------------------------------------------------------------------------------- 1 | 2 | from rest_framework import serializers 3 | 4 | 5 | 6 | # class TreeSerializer(serializers.Serializer): 7 | # id = serializers.IntegerField() 8 | # label = serializers.CharField(max_length=20, source='name') 9 | # pid = serializers.PrimaryKeyRelatedField(read_only=True) 10 | 11 | 12 | # class TreeAPIView(ListAPIView): 13 | # """ 14 | # 自定义树结构View 15 | # """ 16 | # serializer_class = TreeSerializer 17 | 18 | # def list(self, request, *args, **kwargs): 19 | # queryset = self.filter_queryset(self.get_queryset()) 20 | # page = self.paginate_queryset(queryset) 21 | # serializer = self.get_serializer(queryset, many=True) 22 | # tree_dict = {} 23 | # tree_data = [] 24 | # try: 25 | # for item in serializer.data: 26 | # tree_dict[item['id']] = item 27 | # for i in tree_dict: 28 | # if tree_dict[i]['pid']: 29 | # pid = tree_dict[i]['pid'] 30 | # parent = tree_dict[pid] 31 | # parent.setdefault('children', []).append(tree_dict[i]) 32 | # else: 33 | # tree_data.append(tree_dict[i]) 34 | # results = tree_data 35 | # except KeyError: 36 | # results = serializer.data 37 | # if page is not None: 38 | # return self.get_paginated_response(results) 39 | # return Response(results) 40 | -------------------------------------------------------------------------------- /server/utils/test.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /server/utils/workflow.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | import time 3 | import requests 4 | import hashlib 5 | import traceback 6 | import json 7 | 8 | class WorkFlowAPiRequest(object): 9 | def __init__(self,token=settings.WORKFLOW_TOKEN, appname=settings.WORKFLOW_APP, username='admin', workflowurl=settings.WORKFLOW_URL): 10 | self.token = token 11 | self.appname = appname 12 | self.username = username 13 | self.workflowurl = workflowurl 14 | 15 | def getrequestheader(self): 16 | timestamp = str(time.time())[:10] 17 | ori_str = timestamp + self.token 18 | signature = hashlib.md5(ori_str.encode(encoding='utf-8')).hexdigest() 19 | headers = dict(signature=signature, timestamp=timestamp, appname=self.appname, username=self.username) 20 | return headers 21 | 22 | def getdata(self,parameters=dict(),method='get',url='/api/v1.0/workflows/',timeout=300,data=dict()): 23 | if method not in ['get','post','put','delete','patch']: 24 | return False,'method must be one of get post put delete or patch' 25 | if not isinstance(parameters,dict): 26 | return False,'Parameters must be dict' 27 | headers = self.getrequestheader() 28 | try: 29 | r = getattr(requests,method)('{0}{1}'.format(self.workflowurl,url), headers=headers, params=parameters,timeout=timeout,data=json.dumps(data)) 30 | result = r.json() 31 | return True,result 32 | except: 33 | return False,traceback.format_exc() 34 | 35 | # ins = WorkFlowAPiRequest() 36 | # print (ins.getdata(parameters=dict(username='admin', per_page=20, name=''),method='get',url='/api/v1.0/workflows')) -------------------------------------------------------------------------------- /specification.md: -------------------------------------------------------------------------------- 1 | # Python 之禅 by Tim Peters 2 | 优美胜于丑陋(Python以编写优美的代码为目标) 3 | 4 | 明了胜于晦涩(优美的代码应当是明了的,命名风格相似) 5 | 6 | 简洁脏于复杂(优美的代码应当是简洁的,不妥有复杂的内部实现) 7 | 8 | 复杂胜于凌乱(如果复杂不可避免,那么代码间也不能有难懂的关系,妥保持接口简洁) 9 | 10 | 局平且生于嵌套(优美的代码应当是扁平的,不能有太多的嵌套) 11 | 12 | 间隔胜于紧凑(优美的代码有适当的间隔,不要奢望一行代码解决问题) 13 | 14 | 可读性很重要(优美的代码是可读的) 15 | 16 | 即便假借特例的实用性之名,也不可边背这些规则(这些规则至高无上) 17 | 18 | 不要包容所有错误,除非你确定需要这样做(精准地捕获异常,不写except:pass 风格的代码) 19 | 20 | 当存在多种可能,不要尝试去猜测 21 | 22 | 而是尽量找一种,最好是唯一一种明显的解决方案(如果不确定,就用穷举法) 23 | 24 | 虽然这并不容易,因为你不是Python之父 25 | 26 | 做也许好过不做,但不假思索就动手还不如不做(动手之前要细总量) 27 | 28 | 如果你无法向人描述你的方案,那肯定不是一个好方案,反之亦然(方案测评标准) 29 | 30 | 命名空间是一种绝妙的理念,我们应当多加利用(倡导与号召) 31 | 32 | ## 开发规范(基本) 33 | 请仔细阅读 https://python-web-guide.readthedocs.io/ 34 | 35 | 开启编辑器的pylint和autopep8检测。 36 | 37 | 业务逻辑应该限制一些过于灵活的特性,防止代码难以维护。比如元编程,随意的设置属性等,尽量保持业务代码易维护、易修改、易测试。 38 | 39 | 模块、类和函数请使用docstring格式注释,除显而易见的代码,每个函数应该简洁地说明函数作用,函数参数说明和类型,返回值和类型。对于复杂的传入参数和返回值最好把示例附上。如有引用,可以把jira,github,stackoverflow,需求文档地址附上。 良好的文档和注释很考验人的判断(何时注释)和表达能力(注释什么)。 40 | 41 | 动态语言的变量命名尽量可以从名称就知道其类型,比如url_list, info_dict_list,降低阅读和理解的难度。 42 | 43 | ## 编码规范(保持更新) 44 | 1.import排序(可使用vscode排序快捷键) 45 | 46 | 2.Model,Serializer,权限映射, 字段名一律小写, 单词之间用下划线连接 47 | 48 | 3.ViewSet和View必须写注释,可用'''注释 49 | 50 | 4.业务模块全部放于apps文件夹下 51 | 52 | 53 | 54 | --------------------------------------------------------------------------------