├── easy-test-vue ├── src │ ├── models │ │ ├── .gitkeep │ │ └── book.js │ ├── lin │ │ ├── utils │ │ │ ├── index.js │ │ │ ├── storage.js │ │ │ ├── date.js │ │ │ ├── token.js │ │ │ ├── cookie.js │ │ │ ├── search.js │ │ │ └── sse.js │ │ ├── directives │ │ │ ├── index.js │ │ │ ├── README.md │ │ │ ├── authorize.js │ │ │ └── ripple.js │ │ ├── filter │ │ │ └── README.md │ │ ├── mixin │ │ │ ├── README.md │ │ │ └── index.js │ │ ├── plugins │ │ │ ├── README.md │ │ │ ├── index.js │ │ │ ├── preview │ │ │ │ ├── readme.md │ │ │ │ └── index.js │ │ │ └── auto-jump.js │ │ └── models │ │ │ └── notify.js │ ├── assets │ │ ├── styles │ │ │ ├── share.scss │ │ │ ├── realize │ │ │ │ ├── global.scss │ │ │ │ ├── transition.scss │ │ │ │ ├── lin-variables.scss │ │ │ │ ├── mixin.scss │ │ │ │ └── animation.scss │ │ │ └── index.scss │ │ ├── img │ │ │ ├── 小尖角.png │ │ │ ├── logo.png │ │ │ ├── 铃铛icon.png │ │ │ ├── mobile-logo.png │ │ │ ├── user │ │ │ │ ├── corner.png │ │ │ │ ├── user.jpg │ │ │ │ ├── user.png │ │ │ │ └── user-bg.png │ │ │ ├── error-page │ │ │ │ ├── 404.png │ │ │ │ └── logo.png │ │ │ └── login │ │ │ │ ├── login-ba.png │ │ │ │ ├── login-btn.png │ │ │ │ ├── nickname.png │ │ │ │ ├── password.png │ │ │ │ └── team-name.png │ │ └── video │ │ │ └── over.wav │ ├── plugins │ │ ├── LinCmsUi │ │ │ ├── assets │ │ │ │ ├── img │ │ │ │ │ └── logo.png │ │ │ │ └── style │ │ │ │ │ └── container.scss │ │ │ ├── package.json │ │ │ ├── README.md │ │ │ ├── views │ │ │ │ └── table │ │ │ │ │ └── data.js │ │ │ ├── components │ │ │ │ └── Component.vue │ │ │ └── models │ │ │ │ └── movie.js │ │ └── custom │ │ │ ├── assets │ │ │ └── img │ │ │ │ └── logo.png │ │ │ ├── package.json │ │ │ ├── views │ │ │ └── Tinymce.vue │ │ │ ├── README.md │ │ │ └── stage-config.js │ ├── config │ │ ├── stage │ │ │ ├── plugins.js │ │ │ ├── mine.js │ │ │ ├── mock.js │ │ │ ├── postman.js │ │ │ ├── execute.js │ │ │ ├── analysis.js │ │ │ ├── book.js │ │ │ ├── scheduler.js │ │ │ ├── test.js │ │ │ ├── project.js │ │ │ ├── case.js │ │ │ └── admin.js │ │ ├── index.js │ │ └── error-code.js │ ├── components │ │ ├── base │ │ │ ├── sticky-top │ │ │ │ └── sticky-top.vue │ │ │ ├── tinymce │ │ │ │ └── importAll.js │ │ │ ├── dialog │ │ │ │ └── lin-dialog.vue │ │ │ ├── icon │ │ │ │ └── lin-icon.vue │ │ │ ├── dropdown │ │ │ │ └── lin-dropdown.vue │ │ │ ├── source-code │ │ │ │ └── source-code.vue │ │ │ ├── search │ │ │ │ └── lin-search.vue │ │ │ └── date-picker │ │ │ │ └── lin-date-picker.vue │ │ └── layout │ │ │ ├── index.js │ │ │ ├── ClearTab.vue │ │ │ ├── AppMain.vue │ │ │ ├── Breadcrumb.vue │ │ │ ├── NavBar.vue │ │ │ ├── Screenfull.vue │ │ │ ├── MenuTab.vue │ │ │ └── BackTop.vue │ ├── store │ │ ├── mutation-types.js │ │ ├── state.js │ │ ├── actions.js │ │ ├── index.js │ │ └── mutations.js │ ├── router │ │ ├── routes.js │ │ ├── home-router.js │ │ └── index.js │ ├── views │ │ ├── error-page │ │ │ └── 404.vue │ │ ├── admin │ │ │ └── user │ │ │ │ └── UserAdd.vue │ │ └── book │ │ │ └── BookAdd.vue │ ├── App.vue │ └── main.js ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── static │ │ └── img │ │ │ └── logo.png │ └── tinymce │ │ └── skins │ │ ├── ui │ │ └── oxide │ │ │ ├── fonts │ │ │ └── tinymce-mobile.woff │ │ │ └── content.mobile.min.css │ │ └── content │ │ ├── default │ │ ├── content.min.css │ │ └── content.css │ │ ├── writer │ │ ├── content.min.css │ │ └── content.css │ │ └── document │ │ ├── content.min.css │ │ └── content.css ├── .env.production ├── tests │ └── unit │ │ ├── .eslintrc.js │ │ └── LIcon.test.js ├── postcss.config.js ├── .eslintignore ├── .env.development ├── script │ ├── template │ │ ├── plugin │ │ │ ├── assets │ │ │ │ └── img │ │ │ │ │ └── logo.png │ │ │ ├── package.json.ejs │ │ │ ├── README.md.ejs │ │ │ ├── views │ │ │ │ ├── Stage2.vue.ejs │ │ │ │ └── Stage1.vue.ejs │ │ │ ├── stage-config.js.ejs │ │ │ └── components │ │ │ │ └── Component.vue.ejs │ │ └── plugin-stage-config.js.ejs │ ├── lib │ │ ├── exec-promise.js │ │ ├── semver-validate.js │ │ ├── plugin-get-all.js │ │ └── install-dep.js │ └── plugin-get-config.js ├── .prettierrc.js ├── .travis.yml ├── Dockerfile ├── babel.config.js ├── jest.config.js ├── vue.config.js ├── LICENSE ├── default.conf └── .eslintrc.js ├── easy-test-flask ├── app │ ├── libs │ │ ├── __init__.py │ │ ├── job.py │ │ ├── lin_response.py │ │ ├── lin_flask.py │ │ ├── init.py │ │ ├── opreation_excel.py │ │ ├── customize_deal.py │ │ ├── case_uploader.py │ │ ├── error_code.py │ │ └── utils.py │ ├── plugins │ │ ├── poem │ │ │ ├── requirements.txt │ │ │ ├── config.py │ │ │ ├── info.py │ │ │ ├── app │ │ │ │ ├── forms.py │ │ │ │ ├── controller.py │ │ │ │ └── model.py │ │ │ └── README.md │ │ └── oss │ │ │ ├── requirements.txt │ │ │ ├── app │ │ │ ├── __init__.py │ │ │ ├── enums.py │ │ │ ├── model.py │ │ │ └── oss.py │ │ │ ├── info.py │ │ │ ├── config.py │ │ │ └── README.md │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── mock │ │ │ ├── __init__.py │ │ │ └── mock.py │ │ ├── cms │ │ │ ├── file.py │ │ │ └── __init__.py │ │ └── v1 │ │ │ ├── __init__.py │ │ │ ├── CaseGroup.py │ │ │ ├── user.py │ │ │ ├── book.py │ │ │ ├── overview.py │ │ │ ├── mock.py │ │ │ └── scheduler.py │ ├── validators │ │ ├── __init__.py │ │ ├── MockForm.py │ │ ├── MineForm.py │ │ ├── TaskForm.py │ │ └── SchedulerForm.py │ ├── document │ │ └── excel │ │ │ └── template │ │ │ └── caseUploadTemplate.xlsx │ ├── extensions │ │ └── file │ │ │ ├── config.py │ │ │ └── local_uploader.py │ ├── config │ │ ├── log.py │ │ └── secure.py │ └── models │ │ ├── user.py │ │ └── UserAuth.py ├── tests │ ├── utils.py │ ├── token.json │ ├── test_user.py │ ├── test_admin.py │ └── test_book.py ├── docker-entrypoint.sh ├── Dockerfile ├── code.md ├── add_super.py ├── requirements.txt ├── LICENSE ├── Pipfile ├── starter.py └── vendor │ └── plugin_generator.py ├── easy-test-mini ├── pages │ ├── test │ │ ├── test.wxss │ │ ├── test.json │ │ ├── test.wxml │ │ └── test.js │ ├── case │ │ ├── case.json │ │ ├── case.wxss │ │ └── case.wxml │ ├── home │ │ ├── home.json │ │ └── home.wxss │ ├── case-detail │ │ ├── case-detail.json │ │ ├── case-detail.wxss │ │ └── case-detail.js │ ├── logs │ │ ├── logs.json │ │ ├── logs.wxss │ │ ├── logs.wxml │ │ └── logs.js │ └── index │ │ ├── index.json │ │ ├── index.wxss │ │ └── index.wxml ├── ec-canvas │ ├── ec-canvas.json │ ├── ec-canvas.wxss │ ├── ec-canvas.wxml │ └── wx-canvas.js ├── images │ ├── banner.png │ └── tab │ │ ├── case.png │ │ ├── home.png │ │ ├── test.png │ │ ├── case-select.png │ │ ├── home-select.png │ │ └── test-select.png ├── sitemap.json ├── app.wxss ├── package.json ├── app.js ├── utils │ └── util.js ├── .eslintrc.js ├── project.private.config.json ├── project.config.json ├── app.json └── iconfont.wxss ├── mysql ├── mysql.cnf └── init.sql └── .gitignore /easy-test-vue/src/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /easy-test-flask/app/libs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /easy-test-flask/app/plugins/poem/requirements.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /easy-test-flask/app/plugins/poem/config.py: -------------------------------------------------------------------------------- 1 | limit = 20 2 | -------------------------------------------------------------------------------- /easy-test-flask/app/plugins/oss/requirements.txt: -------------------------------------------------------------------------------- 1 | oss2==2.6.1 -------------------------------------------------------------------------------- /easy-test-mini/pages/test/test.wxss: -------------------------------------------------------------------------------- 1 | /* pages/test/test.wxss */ -------------------------------------------------------------------------------- /easy-test-vue/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /easy-test-vue/src/lin/utils/index.js: -------------------------------------------------------------------------------- 1 | export * from './date' 2 | -------------------------------------------------------------------------------- /easy-test-mini/pages/case/case.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {} 3 | } -------------------------------------------------------------------------------- /easy-test-mini/pages/home/home.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {} 3 | } -------------------------------------------------------------------------------- /easy-test-mini/pages/test/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | } 4 | } -------------------------------------------------------------------------------- /easy-test-mini/pages/case-detail/case-detail.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {} 3 | } -------------------------------------------------------------------------------- /easy-test-vue/src/lin/directives/index.js: -------------------------------------------------------------------------------- 1 | import './ripple' 2 | import './authorize' 3 | -------------------------------------------------------------------------------- /easy-test-vue/.env.production: -------------------------------------------------------------------------------- 1 | 2 | 3 | VUE_APP_BASE_URL = '/' 4 | VUE_APP_SOCKETIO_URL = '/' 5 | -------------------------------------------------------------------------------- /easy-test-vue/src/lin/filter/README.md: -------------------------------------------------------------------------------- 1 | # Lin-filter 2 | 3 | 文件夹内容描述 4 | 5 | 更多内容请查看 [文档](#) 6 | -------------------------------------------------------------------------------- /easy-test-vue/src/lin/mixin/README.md: -------------------------------------------------------------------------------- 1 | # Lin-mixin 2 | 3 | 文件夹内容描述 4 | 5 | 更多内容请查看 [文档](#) 6 | -------------------------------------------------------------------------------- /easy-test-mini/ec-canvas/ec-canvas.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /easy-test-mini/ec-canvas/ec-canvas.wxss: -------------------------------------------------------------------------------- 1 | .ec-canvas { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | -------------------------------------------------------------------------------- /easy-test-vue/src/lin/plugins/README.md: -------------------------------------------------------------------------------- 1 | # Lin-plugins 2 | 3 | 文件夹内容描述 4 | 5 | 更多内容请查看 [文档](#) 6 | -------------------------------------------------------------------------------- /easy-test-vue/tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | } -------------------------------------------------------------------------------- /easy-test-flask/app/plugins/oss/app/__init__.py: -------------------------------------------------------------------------------- 1 | from .controller import api 2 | from .model import Image 3 | -------------------------------------------------------------------------------- /easy-test-vue/src/lin/directives/README.md: -------------------------------------------------------------------------------- 1 | # Lin-directives 2 | 3 | 文件夹内容描述 4 | 5 | 更多内容请查看 [文档](#) 6 | -------------------------------------------------------------------------------- /easy-test-vue/src/lin/plugins/index.js: -------------------------------------------------------------------------------- 1 | import './auto-jump' 2 | import './axios' 3 | import './preview' 4 | -------------------------------------------------------------------------------- /easy-test-mini/pages/logs/logs.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "查看启动日志", 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /easy-test-vue/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {}, 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /easy-test-flask/app/plugins/oss/info.py: -------------------------------------------------------------------------------- 1 | __name__ = 'oss' 2 | __version__ = '0.0.1' 3 | __author__ = 'Lin team' 4 | -------------------------------------------------------------------------------- /easy-test-flask/app/plugins/poem/info.py: -------------------------------------------------------------------------------- 1 | __name__ = 'poem' 2 | __version__ = '0.0.1' 3 | __author__ = 'Lin team' 4 | -------------------------------------------------------------------------------- /easy-test-vue/src/assets/styles/share.scss: -------------------------------------------------------------------------------- 1 | 2 | @import "./realize/lin-variables"; 3 | @import "./realize/mixin"; 4 | -------------------------------------------------------------------------------- /easy-test-mini/images/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-mini/images/banner.png -------------------------------------------------------------------------------- /easy-test-mini/images/tab/case.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-mini/images/tab/case.png -------------------------------------------------------------------------------- /easy-test-mini/images/tab/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-mini/images/tab/home.png -------------------------------------------------------------------------------- /easy-test-mini/images/tab/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-mini/images/tab/test.png -------------------------------------------------------------------------------- /easy-test-vue/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/public/favicon.ico -------------------------------------------------------------------------------- /easy-test-mini/pages/test/test.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /easy-test-vue/src/assets/img/小尖角.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/src/assets/img/小尖角.png -------------------------------------------------------------------------------- /easy-test-vue/src/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/src/assets/img/logo.png -------------------------------------------------------------------------------- /easy-test-vue/src/assets/img/铃铛icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/src/assets/img/铃铛icon.png -------------------------------------------------------------------------------- /easy-test-vue/src/assets/video/over.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/src/assets/video/over.wav -------------------------------------------------------------------------------- /easy-test-mini/images/tab/case-select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-mini/images/tab/case-select.png -------------------------------------------------------------------------------- /easy-test-mini/images/tab/home-select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-mini/images/tab/home-select.png -------------------------------------------------------------------------------- /easy-test-mini/images/tab/test-select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-mini/images/tab/test-select.png -------------------------------------------------------------------------------- /easy-test-vue/.eslintignore: -------------------------------------------------------------------------------- 1 | /builds/ 2 | /public/ 3 | /dist/ 4 | /script/.cache 5 | /*.js 6 | /node_modules/ 7 | /tests/unit/LIcon.test.js 8 | -------------------------------------------------------------------------------- /easy-test-vue/public/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/public/static/img/logo.png -------------------------------------------------------------------------------- /easy-test-flask/app/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | :copyright: © 2019 by the Lin team. 3 | :license: MIT, see LICENSE for more details. 4 | """ 5 | -------------------------------------------------------------------------------- /easy-test-flask/app/api/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | :copyright: © 2019 by the Lin team. 3 | :license: MIT, see LICENSE for more details. 4 | """ 5 | -------------------------------------------------------------------------------- /easy-test-vue/src/assets/img/mobile-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/src/assets/img/mobile-logo.png -------------------------------------------------------------------------------- /easy-test-vue/src/assets/img/user/corner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/src/assets/img/user/corner.png -------------------------------------------------------------------------------- /easy-test-vue/src/assets/img/user/user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/src/assets/img/user/user.jpg -------------------------------------------------------------------------------- /easy-test-vue/src/assets/img/user/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/src/assets/img/user/user.png -------------------------------------------------------------------------------- /easy-test-flask/app/validators/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | :copyright: © 2019 by the Lin team. 3 | :license: MIT, see LICENSE for more details. 4 | """ -------------------------------------------------------------------------------- /easy-test-vue/src/assets/img/user/user-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/src/assets/img/user/user-bg.png -------------------------------------------------------------------------------- /easy-test-vue/src/assets/img/error-page/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/src/assets/img/error-page/404.png -------------------------------------------------------------------------------- /easy-test-vue/src/assets/img/error-page/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/src/assets/img/error-page/logo.png -------------------------------------------------------------------------------- /easy-test-vue/src/assets/img/login/login-ba.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/src/assets/img/login/login-ba.png -------------------------------------------------------------------------------- /easy-test-vue/src/assets/img/login/login-btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/src/assets/img/login/login-btn.png -------------------------------------------------------------------------------- /easy-test-vue/src/assets/img/login/nickname.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/src/assets/img/login/nickname.png -------------------------------------------------------------------------------- /easy-test-vue/src/assets/img/login/password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/src/assets/img/login/password.png -------------------------------------------------------------------------------- /easy-test-vue/src/assets/img/login/team-name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/src/assets/img/login/team-name.png -------------------------------------------------------------------------------- /easy-test-vue/.env.development: -------------------------------------------------------------------------------- 1 | ENV = 'development' 2 | 3 | VUE_APP_BASE_URL = 'http://127.0.0.1:5000/' 4 | 5 | VUE_APP_SOCKETIO_URL = 'http://127.0.0.1:5000/' -------------------------------------------------------------------------------- /easy-test-vue/src/plugins/LinCmsUi/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/src/plugins/LinCmsUi/assets/img/logo.png -------------------------------------------------------------------------------- /easy-test-vue/src/plugins/custom/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/src/plugins/custom/assets/img/logo.png -------------------------------------------------------------------------------- /easy-test-mini/pages/logs/logs.wxss: -------------------------------------------------------------------------------- 1 | .log-list { 2 | display: flex; 3 | flex-direction: column; 4 | padding: 40rpx; 5 | } 6 | .log-item { 7 | margin: 10rpx; 8 | } 9 | -------------------------------------------------------------------------------- /easy-test-vue/script/template/plugin/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/script/template/plugin/assets/img/logo.png -------------------------------------------------------------------------------- /easy-test-flask/app/plugins/oss/app/enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | # 图片保存在本地还是云端 5 | # 建议云端 6 | class LocalOrCloud(Enum): 7 | LOCAL = 1 8 | CLOUD = 2 9 | -------------------------------------------------------------------------------- /easy-test-flask/app/document/excel/template/caseUploadTemplate.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-flask/app/document/excel/template/caseUploadTemplate.xlsx -------------------------------------------------------------------------------- /easy-test-vue/public/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guojiaxing1995/easy-test/HEAD/easy-test-vue/public/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff -------------------------------------------------------------------------------- /easy-test-mini/sitemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", 3 | "rules": [{ 4 | "action": "allow", 5 | "page": "*" 6 | }] 7 | } -------------------------------------------------------------------------------- /easy-test-vue/src/assets/styles/realize/global.scss: -------------------------------------------------------------------------------- 1 | 2 | .fn-ellpisis1{ 3 | @include fn-ellpisis(1) 4 | } 5 | .fn-ellpisis2{ 6 | @include fn-ellpisis(2) 7 | } 8 | .fn-ellpisis3{ 9 | @include fn-ellpisis(3) 10 | } 11 | -------------------------------------------------------------------------------- /easy-test-mini/pages/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "backgroundTextStyle": "light", 3 | "navigationBarBackgroundColor": "#fff", 4 | "navigationBarTitleText": "easy-test", 5 | "navigationBarTextStyle": "black", 6 | "usingComponents": {} 7 | } -------------------------------------------------------------------------------- /easy-test-mini/pages/logs/logs.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{index + 1}}. {{log.date}} 5 | 6 | 7 | -------------------------------------------------------------------------------- /easy-test-flask/app/api/mock/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | from app.api.mock import mock 3 | 4 | 5 | def create_mock(): 6 | bp_mock = Blueprint('mock', __name__) 7 | mock.mock_api.register(bp_mock) 8 | 9 | return bp_mock 10 | -------------------------------------------------------------------------------- /easy-test-vue/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, //字符串是否使用单引号,默认为false,使用双引号 3 | semi: false, //行位是否使用分号,默认为true 4 | trailingComma: 'all', //是否使用尾逗号,有三个可选值"" 5 | printWidth: 120, 6 | arrowParens: 'avoid', 7 | } 8 | -------------------------------------------------------------------------------- /easy-test-vue/src/assets/styles/realize/transition.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | // 4 | .fade-enter-active, .fade-leave-active { 5 | transition: opacity .5s; 6 | } 7 | .fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ { 8 | opacity: 0; 9 | } 10 | -------------------------------------------------------------------------------- /easy-test-flask/app/extensions/file/config.py: -------------------------------------------------------------------------------- 1 | # 文件相关配置 2 | FILE = { 3 | "STORE_DIR": 'app/assets', 4 | "SINGLE_LIMIT": 1024 * 1024 * 2, 5 | "TOTAL_LIMIT": 1024 * 1024 * 20, 6 | "NUMS": 10, 7 | "INCLUDE": set([]), 8 | "EXCLUDE": set([]) 9 | } 10 | -------------------------------------------------------------------------------- /easy-test-flask/app/plugins/oss/config.py: -------------------------------------------------------------------------------- 1 | access_key_id = 'not complete' 2 | access_key_secret = 'not complete' 3 | endpoint = 'http://oss-cn-shenzhen.aliyuncs.com' 4 | bucket_name = 'not complete' 5 | 6 | upload_folder = 'app' 7 | allowed_extensions = ['jpg', 'gif', 'png', 'bmp'] 8 | -------------------------------------------------------------------------------- /easy-test-vue/src/plugins/LinCmsUi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lc-plugin-lin-cms-ui", 3 | "title": "UI", 4 | "version": "1.0.0", 5 | "_linVersion": "0.0.1-alpha.3", 6 | "description": "", 7 | "author": "", 8 | "dependencies": {}, 9 | "devDependencies": {} 10 | } 11 | -------------------------------------------------------------------------------- /easy-test-vue/src/config/stage/plugins.js: -------------------------------------------------------------------------------- 1 | // 本文件是自动生成, 请勿修改 2 | import custom from '@/plugins/custom/stage-config' 3 | import LinCmsUi from '@/plugins/LinCmsUi/stage-config' 4 | 5 | const pluginsConfig = [ 6 | custom, 7 | LinCmsUi, 8 | ] 9 | 10 | export default pluginsConfig 11 | -------------------------------------------------------------------------------- /easy-test-vue/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | addons: 5 | chrome: stable 6 | sudo: required 7 | before_script: 8 | - "sudo chown root /opt/google/chrome/chrome-sandbox" 9 | - "sudo chmod 4755 /opt/google/chrome/chrome-sandbox" 10 | script: npm run test:unit -------------------------------------------------------------------------------- /easy-test-vue/public/tinymce/skins/ui/oxide/content.mobile.min.css: -------------------------------------------------------------------------------- 1 | .tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{position:absolute;display:inline-block;background-color:green;opacity:.5}body{-webkit-text-size-adjust:none}body img{max-width:96vw}body table img{max-width:95%} -------------------------------------------------------------------------------- /easy-test-vue/src/components/base/sticky-top/sticky-top.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | -------------------------------------------------------------------------------- /easy-test-vue/src/plugins/custom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lc-plugin-custom", 3 | "title": "图像上传组件示例", 4 | "version": "1.0.0", 5 | "_linVersion": "0.0.1-alpha.3", 6 | "description": "展示图像上传组件能力", 7 | "author": "", 8 | "dependencies": {}, 9 | "devDependencies": {} 10 | } 11 | -------------------------------------------------------------------------------- /easy-test-mini/app.wxss: -------------------------------------------------------------------------------- 1 | /**app.wxss**/ 2 | @import '/iconfont.wxss'; 3 | 4 | .container { 5 | height: 100%; 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | justify-content: space-between; 10 | padding: 200rpx 0; 11 | box-sizing: border-box; 12 | } 13 | 14 | 15 | -------------------------------------------------------------------------------- /mysql/mysql.cnf: -------------------------------------------------------------------------------- 1 | [mysqld] 2 | skip_external_locking 3 | lower_case_table_names=1 4 | skip_host_cache 5 | skip_name_resolve 6 | character-set-server = utf8mb4 7 | collation-server = utf8mb4_general_ci 8 | init_connect='SET NAMES utf8mb4' 9 | default-storage-engine=INNODB 10 | default-time_zone = '+8:00' 11 | -------------------------------------------------------------------------------- /easy-test-flask/app/plugins/oss/README.md: -------------------------------------------------------------------------------- 1 | # oss 插件 2 | 3 | ## 插件模板自动生成 4 | 5 | 请在项目的根目录下运行如下命令: 6 | 7 | ```bash 8 | python vendor/plugin_generator.py -n oss 9 | ``` 10 | 11 | 即可快速生成一个名为oss的插件,`-n`表示指定插件名 12 | 13 | ## 添加requirements.txt 14 | 15 | 如果你是在插件中使用了一些第三方库,这些库在主应用中是没有的,那么请你将它添加到**requirements.txt**中。 -------------------------------------------------------------------------------- /easy-test-flask/app/config/log.py: -------------------------------------------------------------------------------- 1 | """ 2 | :copyright: © 2019 by the Lin team. 3 | :license: MIT, see LICENSE for more details. 4 | """ 5 | LOG = { 6 | 'LEVEL': 'DEBUG', 7 | 'DIR': 'logs', 8 | 'SIZE_LIMIT': 1024 * 1024 * 5, 9 | 'REQUEST_LOG': True, 10 | 'FILE': True 11 | } 12 | 13 | 14 | -------------------------------------------------------------------------------- /easy-test-vue/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.17 2 | 3 | RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' > /etc/timezone 4 | 5 | COPY dist/ /usr/share/nginx/html/ 6 | 7 | COPY default.conf /etc/nginx/conf.d/ 8 | 9 | COPY www.xxx.com.pem /home/ 10 | 11 | COPY www.xxx.com.key /home/ 12 | -------------------------------------------------------------------------------- /easy-test-vue/src/components/base/tinymce/importAll.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | const importAll = requireContext => requireContext.keys().forEach(requireContext) 3 | try { 4 | // 导入所有插件 5 | importAll(require.context('../../../../node_modules/tinymce/plugins', true)) 6 | } catch (err) { 7 | console.log(err) 8 | } 9 | -------------------------------------------------------------------------------- /easy-test-vue/script/template/plugin/package.json.ejs: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lc-plugin-<%= name %>", 3 | "title": "<%= title %>", 4 | "version": "<%= version %>", 5 | "_linVersion": "0.0.1-alpha.3", 6 | "description": "<%= description %>", 7 | "author": "<%= author %>", 8 | "dependencies": {}, 9 | "devDependencies": {} 10 | } -------------------------------------------------------------------------------- /easy-test-vue/src/config/stage/mine.js: -------------------------------------------------------------------------------- 1 | const MineRouter = { 2 | route: '/mine/list', 3 | type: 'view', 4 | name: 'MineList', 5 | inNav: true, 6 | filePath: 'views/mine/MineList.vue', 7 | title: '我的数据', 8 | order: 8, 9 | icon: 'iconfont icon-mine', 10 | keepAlive: true, 11 | } 12 | 13 | export default MineRouter 14 | -------------------------------------------------------------------------------- /easy-test-flask/tests/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | def write_token(data): 5 | obj = json.dumps(data) 6 | with open('token.json', 'w') as f: 7 | f.write(obj) 8 | 9 | 10 | def get_token(key='access_token'): 11 | with open('token.json', 'r') as f: 12 | obj = json.loads(f.read()) 13 | return obj[key] 14 | -------------------------------------------------------------------------------- /easy-test-vue/script/template/plugin-stage-config.js.ejs: -------------------------------------------------------------------------------- 1 | // 本文件是自动生成, 请勿修改 2 | <% plugins.forEach(function(plugin){ %>import <%= plugin.name %> from '@/plugins/<%= plugin.name %>/stage-config' 3 | <% }); %> 4 | const pluginsConfig = [ 5 | <% plugins.forEach(function(plugin){ %> <%= plugin.name %>, 6 | <% }); %>] 7 | 8 | export default pluginsConfig 9 | -------------------------------------------------------------------------------- /easy-test-flask/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | if [ $1 = "api" ];then 5 | python starter.py --env=prod 6 | elif [ $1 = "worker" ];then 7 | celery -A starter.celery worker -l info --pool=solo -f logs/celery.log --host=prod 8 | else 9 | echo "参数错误 1) api 作为后端主程序启动. 2)worker 作为消费者启动" 10 | exit 1 11 | fi -------------------------------------------------------------------------------- /easy-test-vue/src/components/layout/index.js: -------------------------------------------------------------------------------- 1 | import NavBar from './NavBar' 2 | import SideBar from './SideBar' 3 | import AppMain from './AppMain' 4 | import ReuseTab from './ReuseTab' 5 | import MenuTab from './MenuTab.vue' 6 | import BackTop from './BackTop.vue' 7 | import User from './User.vue' 8 | 9 | export { NavBar, SideBar, AppMain, ReuseTab, MenuTab, BackTop, User } 10 | -------------------------------------------------------------------------------- /easy-test-vue/script/lib/exec-promise.js: -------------------------------------------------------------------------------- 1 | const child_process = require('child_process') 2 | 3 | // 执行命令 4 | function exec(cmd) { 5 | return new Promise((resolve, reject) => { 6 | child_process.exec(cmd, (error, stdout) => { 7 | if (error) { 8 | reject(error) 9 | } 10 | resolve(stdout) 11 | }) 12 | }) 13 | } 14 | 15 | module.exports = exec 16 | -------------------------------------------------------------------------------- /easy-test-vue/src/config/stage/mock.js: -------------------------------------------------------------------------------- 1 | const mockRouter = { 2 | route: '/mock', 3 | name: 'mock', 4 | title: 'mock管理', 5 | type: 'view', // 类型: folder, tab, view 6 | icon: 'iconfont icon-mock', 7 | filePath: 'views/mock/MockManage.vue', // 文件路径 8 | order: 9, 9 | inNav: true, 10 | keepAlive: true, 11 | permission: ['mock'], 12 | } 13 | 14 | export default mockRouter 15 | -------------------------------------------------------------------------------- /easy-test-flask/app/plugins/oss/app/model.py: -------------------------------------------------------------------------------- 1 | from lin.interface import BaseCrud 2 | from sqlalchemy import Column, Integer, String, FetchedValue 3 | 4 | 5 | class Image(BaseCrud): 6 | __tablename__ = 'image' 7 | 8 | id = Column(Integer, primary_key=True) 9 | url = Column(String(255), nullable=False) 10 | _from = Column('from', Integer, nullable=False, server_default=FetchedValue()) 11 | -------------------------------------------------------------------------------- /easy-test-vue/babel.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable comma-dangle */ 2 | module.exports = { 3 | presets: [ 4 | [ 5 | '@vue/app', 6 | { 7 | useBuiltIns: 'entry' 8 | } 9 | ] 10 | ], 11 | plugins: [ 12 | [ 13 | 'component', 14 | { 15 | libraryName: 'element-ui', 16 | styleLibraryName: 'theme-chalk', 17 | }, 18 | ], 19 | ], 20 | } 21 | -------------------------------------------------------------------------------- /easy-test-vue/src/config/stage/postman.js: -------------------------------------------------------------------------------- 1 | const postmanRouter = { 2 | route: '/postman', 3 | name: 'postman', 4 | title: 'PostMan', 5 | type: 'view', // 类型: folder, tab, view 6 | icon: 'iconfont icon-postman', 7 | filePath: 'views/postman/PostMan.vue', // 文件路径 8 | order: 1, 9 | inNav: true, 10 | keepAlive: true, 11 | // permission: [''], 12 | } 13 | 14 | export default postmanRouter 15 | -------------------------------------------------------------------------------- /easy-test-mini/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "easy-test-mini", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": ".eslintrc.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "lin-ui": "^0.9.7", 13 | "weapp.socket.io": "^3.0.0" 14 | }, 15 | "devDependencies": {} 16 | } 17 | -------------------------------------------------------------------------------- /easy-test-vue/src/lin/utils/storage.js: -------------------------------------------------------------------------------- 1 | import storage from 'good-storage' 2 | 3 | const LOGIN_KEY = '__login__' 4 | 5 | export function setLogined(flag) { 6 | storage.session.set(LOGIN_KEY, flag) 7 | return flag 8 | } 9 | 10 | export function loadLogined() { 11 | return storage.session.get(LOGIN_KEY, '') 12 | } 13 | 14 | export function cleanLogined() { 15 | storage.session.remove(LOGIN_KEY) 16 | } 17 | -------------------------------------------------------------------------------- /easy-test-mini/pages/logs/logs.js: -------------------------------------------------------------------------------- 1 | // logs.js 2 | const util = require('../../utils/util.js') 3 | 4 | Page({ 5 | data: { 6 | logs: [] 7 | }, 8 | onLoad() { 9 | this.setData({ 10 | logs: (wx.getStorageSync('logs') || []).map(log => { 11 | return { 12 | date: util.formatTime(new Date(log)), 13 | timeStamp: log 14 | } 15 | }) 16 | }) 17 | } 18 | }) 19 | -------------------------------------------------------------------------------- /easy-test-vue/src/config/stage/execute.js: -------------------------------------------------------------------------------- 1 | const executeRouter = { 2 | route: '/execute/test', 3 | name: 'executeTest', 4 | title: '测试执行', 5 | type: 'view', // 类型: folder, tab, view 6 | icon: 'iconfont icon-execute', 7 | filePath: 'views/execute/ExecuteTest.vue', // 文件路径 8 | order: 2, 9 | inNav: true, 10 | keepAlive: true, 11 | // permission: [''], 12 | } 13 | 14 | export default executeRouter 15 | -------------------------------------------------------------------------------- /easy-test-vue/src/config/index.js: -------------------------------------------------------------------------------- 1 | const Config = { 2 | baseURL: process.env.VUE_APP_BASE_URL, 3 | stagnateTime: 1 * 60 * 60 * 1000, // 无操作停滞时间 默认1小时 4 | openAutoJumpOut: true, // 是否开启无操作跳出 5 | notLoginRoute: ['login'], // 无需登录即可访问的路由 name, 6 | sideBarLevel: 3, // 侧边栏层级限制, 3表示三级, 可设置 2 和 3 7 | showSidebarSearch: true, // 默认打开侧边栏搜索 8 | defaultRoute: '/about', // 默认打开的路由 9 | } 10 | 11 | export default Config 12 | -------------------------------------------------------------------------------- /easy-test-vue/src/config/stage/analysis.js: -------------------------------------------------------------------------------- 1 | const analysisRouter = { 2 | route: '/analysis/case', 3 | name: 'analysis', 4 | title: '用例分析', 5 | type: 'view', // 类型: folder, tab, view 6 | icon: 'iconfont icon-analysis', 7 | filePath: 'views/analysis/CaseAnalysis.vue', // 文件路径 8 | order: 7, 9 | inNav: true, 10 | keepAlive: true, 11 | permission: ['用例分析'], 12 | } 13 | 14 | export default analysisRouter 15 | -------------------------------------------------------------------------------- /easy-test-vue/src/components/base/dialog/lin-dialog.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /easy-test-flask/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.6-slim 2 | 3 | MAINTAINER guojiaxing<302802003@qq.com> 4 | 5 | EXPOSE 5000 6 | 7 | COPY . ./easy-test-flask/ 8 | 9 | WORKDIR /easy-test-flask/ 10 | 11 | RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' > /etc/timezone 12 | 13 | RUN ["pip", "install", "-i", "https://pypi.tuna.tsinghua.edu.cn/simple", "-r", "requirements.txt"] 14 | 15 | ENTRYPOINT ["sh", "./docker-entrypoint.sh"] 16 | 17 | CMD ["api"] 18 | -------------------------------------------------------------------------------- /easy-test-vue/src/config/error-code.js: -------------------------------------------------------------------------------- 1 | const errorCode = { 2 | // 框架内置错误码 3 | 999: '服务器未知错误', 4 | 10000: '认证失败', 5 | 10007: '其他框架内置错误', 6 | 10020: '资源不存在', 7 | 10030: '参数错误', 8 | 10040: 'assessToken令牌失效', 9 | 10050: 'assessToken令牌过期', 10 | 10060: '字段重复', 11 | 10070: '不可操作', 12 | 10100: 'refreshToken异常', 13 | 10110: '文件数量过多', 14 | 10120: 'refreshToken异常', 15 | 10130: '文件扩展名不符合规范', 16 | // 业务错误码 17 | 91001: '删除测试用例分组失败', 18 | } 19 | 20 | export default errorCode 21 | -------------------------------------------------------------------------------- /easy-test-vue/src/lin/utils/date.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | 3 | // 设置语言为中文 4 | moment.locale('zh-cn') 5 | 6 | /** 7 | * @param {number} hours 8 | */ 9 | export function getDateAfterHours(hours) { 10 | const now = new Date() 11 | return new Date(now.setHours(now.getHours() + hours)) 12 | } 13 | /** 14 | * @param {number} days 15 | */ 16 | export function getDateAfterDays(days) { 17 | const now = new Date() 18 | return new Date(now.setHours(now.getHours() + days * 24)) 19 | } 20 | -------------------------------------------------------------------------------- /easy-test-vue/src/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const SET_LOGINED = 'SET_LOGINED' 2 | 3 | export const REMOVE_LOGINED = 'REMOVE_LOGINED' 4 | 5 | export const SET_USER = 'SET_USER' 6 | 7 | export const ADD_READED_MESSAGE = 'ADD_READED_MESSAGE' 8 | 9 | export const REMOVE_UNREAD_MESSAGE = 'REMOVE_UNREAD_MESSAGE' 10 | 11 | export const ADD_UNREAD_MESSAGE = 'ADD_UNREAD_MESSAGE' 12 | 13 | export const SET_USER_AUTHS = 'SET_USER_AUTHS' 14 | 15 | export const SET_REFERSH_OPTION = 'SET_REFERSH_OPTION' 16 | -------------------------------------------------------------------------------- /easy-test-vue/src/router/routes.js: -------------------------------------------------------------------------------- 1 | import Home from '@/views/home/Home' 2 | import homeRouter from './home-router' 3 | 4 | const routes = [ 5 | { 6 | path: '/', 7 | name: 'Home', 8 | redirect: '/about', 9 | component: Home, 10 | children: [...homeRouter], 11 | }, 12 | { 13 | path: '/login', 14 | name: 'login', 15 | component: () => import('@/views/login/Login'), 16 | }, 17 | { 18 | redirect: '/404', 19 | path: '*', 20 | }, 21 | ] 22 | 23 | export default routes 24 | -------------------------------------------------------------------------------- /easy-test-mini/app.js: -------------------------------------------------------------------------------- 1 | // app.js 2 | App({ 3 | onLaunch() { 4 | // 展示本地存储能力 5 | const logs = wx.getStorageSync('logs') || [] 6 | logs.unshift(Date.now()) 7 | wx.setStorageSync('logs', logs) 8 | 9 | // 登录 10 | wx.login({ 11 | success: res => { 12 | // 发送 res.code 到后台换取 openId, sessionKey, unionId 13 | } 14 | }) 15 | }, 16 | globalData: { 17 | userInfo: null, 18 | apiBase: "https://www.guojiaxing.red", 19 | //apiBase: "http://127.0.0.1:5000" 20 | } 21 | }) 22 | -------------------------------------------------------------------------------- /easy-test-mini/pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | /**index.wxss**/ 2 | .userinfo { 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | color: #666; 7 | } 8 | 9 | .userinfo-avatar { 10 | overflow: hidden; 11 | width: 200rpx; 12 | height: 200rpx; 13 | margin: 20rpx; 14 | border-radius: 50%; 15 | } 16 | 17 | .usermotto { 18 | margin-top: 200px; 19 | } 20 | 21 | #copyright{ 22 | position: absolute; 23 | bottom: 2%; 24 | font-size: 20rpx; 25 | color: #9a9b9c; 26 | font-weight: 400; 27 | } -------------------------------------------------------------------------------- /easy-test-vue/src/assets/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './realize/reset'; 2 | @import './realize/animation'; 3 | @import './realize/transition'; 4 | @import './realize/global'; 5 | 6 | .lin-container { 7 | .el-divider--horizontal { 8 | margin:0px; 9 | } 10 | .lin-title { 11 | height: 59px; 12 | line-height: 59px; 13 | color: $parent-title-color; 14 | font-size: 16px; 15 | font-weight: 500; 16 | text-indent: 40px; 17 | border-bottom: 1px solid #dae1ed; 18 | } 19 | 20 | .lin-wrap { 21 | padding: 20px; 22 | } 23 | } -------------------------------------------------------------------------------- /easy-test-mini/utils/util.js: -------------------------------------------------------------------------------- 1 | const formatTime = date => { 2 | const year = date.getFullYear() 3 | const month = date.getMonth() + 1 4 | const day = date.getDate() 5 | const hour = date.getHours() 6 | const minute = date.getMinutes() 7 | const second = date.getSeconds() 8 | 9 | return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}` 10 | } 11 | 12 | const formatNumber = n => { 13 | n = n.toString() 14 | return n[1] ? n : `0${n}` 15 | } 16 | 17 | module.exports = { 18 | formatTime 19 | } 20 | -------------------------------------------------------------------------------- /easy-test-vue/src/store/state.js: -------------------------------------------------------------------------------- 1 | import stageConfig from '@/config/stage' // 引入舞台配置 2 | import AppConfig from '@/config/index' // 引入项目配置 3 | 4 | export default { 5 | logined: false, // 是否登录 6 | user: null, // 当前用户 7 | sideBarLevel: AppConfig.sideBarLevel || 3, 8 | defaultRoute: AppConfig.defaultRoute || '/about', 9 | 10 | // 推送消息 11 | readedMessages: [], 12 | unreadMessages: [], 13 | auths: [], // 每个用户的所有权限 14 | 15 | // 舞台配置 16 | stageConfig, 17 | // 当前页信息 18 | currentRoute: { 19 | config: null, 20 | treePath: [], 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /easy-test-flask/tests/token.json: -------------------------------------------------------------------------------- 1 | {"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1NDg4MzQxNzQsIm5iZiI6MTU0ODgzNDE3NCwianRpIjoiNWI5OTAyMTAtZDg1YS00ZGUzLTgwMWQtYTAwMzc0ZDViODZkIiwiZXhwIjoxNTQ4ODM3Nzc0LCJpZGVudGl0eSI6MSwiZnJlc2giOmZhbHNlLCJ0eXBlIjoiYWNjZXNzIn0.CXd3YAKstysb3SoU5yv7btdToSnCOnDnyenCrjKDZpM", "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1NDg4MzQxNzQsIm5iZiI6MTU0ODgzNDE3NCwianRpIjoiOTcyZjEwZjMtYWJiYy00NWUyLWFkMzQtYTY4ZTIyNzc3OGYwIiwiZXhwIjoxNTUxNDI2MTc0LCJpZGVudGl0eSI6MSwidHlwZSI6InJlZnJlc2gifQ.sqN6tD5exnhS6BmBQcsZKZlFHWnodY9nkGAKjFEYD9g"} -------------------------------------------------------------------------------- /easy-test-flask/code.md: -------------------------------------------------------------------------------- 1 | # code 2 | 3 | ## 核心库内置已使用状态码 4 | 5 | 0 成功 6 | 7 | 999 服务器未知错误 8 | 9 | 10000 认证失败 10 | 11 | 10007 其他框架内置错误 12 | 13 | 10020 资源不存在 14 | 15 | 10030 参数错误 16 | 17 | 10040 令牌失效 18 | 19 | 10050 令牌过期 20 | 21 | 10060 字段重复 22 | 23 | 10070 禁止操作 24 | 25 | 10100 refresh token 获取失败 26 | 27 | 10110 文件体积过大 28 | 29 | 10120 文件数量过多 30 | 31 | 10130 文件扩展名不符合规范 32 | 33 | 20000 werkzeug 中的HTTP EXCEPTION,error_code统一为1007,前端应读取msg 34 | 35 | ## 项目使用的状态码 36 | 37 | 80010 图书未找到 38 | 39 | 91001 删除测试用例分组失败 40 | 41 | 92002 配置不存在 42 | 43 | 93001 用例删除失败 44 | 45 | 94001 运行记录删除失败 -------------------------------------------------------------------------------- /easy-test-flask/app/api/cms/file.py: -------------------------------------------------------------------------------- 1 | """ 2 | :copyright: © 2019 by the Lin team. 3 | :license: MIT, see LICENSE for more details. 4 | """ 5 | from flask import request, jsonify 6 | from lin import login_required 7 | from lin.redprint import Redprint 8 | 9 | from app.extensions.file.local_uploader import LocalUploader 10 | 11 | file_api = Redprint('file') 12 | 13 | 14 | @file_api.route('', methods=['POST']) 15 | @login_required 16 | def post_file(): 17 | files = request.files 18 | uploader = LocalUploader(files) 19 | ret = uploader.upload() 20 | return jsonify(ret) 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Pipfile.lock 2 | __pycache__ 3 | */__pycache__/ 4 | *.pytest_cache/ 5 | *.db 6 | *.pyc 7 | *.cpython-36.pyc 8 | secure.py 9 | 10 | 11 | .DS_Store 12 | node_modules 13 | miniprogram_npm 14 | dist 15 | /script/.cache 16 | build 17 | 18 | # local env files 19 | .env.local 20 | .env.*.local 21 | 22 | # Log files 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | *.log* 27 | 28 | # Editor directories and files 29 | .idea 30 | .vscode 31 | *.suo 32 | *.ntvs* 33 | *.njsproj 34 | *.sln 35 | *.sw* 36 | 37 | # cert 38 | *.pem 39 | www.* 40 | 41 | 20* 42 | upload 43 | download -------------------------------------------------------------------------------- /easy-test-mini/ec-canvas/ec-canvas.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /easy-test-vue/src/components/layout/ClearTab.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | 17 | 28 | -------------------------------------------------------------------------------- /easy-test-vue/src/store/actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './mutation-types' 2 | import { removeToken } from '@/lin/utils/token' 3 | 4 | export default { 5 | setUserAndState({ commit }, user) { 6 | // 如果登陆成功,设置logined标志位 7 | commit(types.SET_LOGINED, true) 8 | // 设置全局用户状态 9 | commit(types.SET_USER, user) 10 | }, 11 | 12 | loginOut({ commit }) { 13 | removeToken() 14 | commit(types.REMOVE_LOGINED, false) 15 | }, 16 | 17 | readMessage({ commit }, message) { 18 | commit(types.REMOVE_UNREAD_MESSAGE, message.id) 19 | commit(types.ADD_READED_MESSAGE, message) 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /easy-test-vue/src/lin/plugins/preview/readme.md: -------------------------------------------------------------------------------- 1 | # 图片预览插件 2 | 3 | ## methods 4 | 5 | ``` 6 | this.$imagePreview(options = {}) 7 | ``` 8 | 9 | options有三个参数 10 | 11 | 参数 | 默认值 | 说明 12 | --- | ---| --- 13 | images | 空数组 | 图片的url数组 14 | index | 0 | 预览图片的索引值, 默认是0 15 | defaultOpt | {} | 配置项 16 | 17 | defaultOpt 的配置项请参考[photoswipe配置项](http://photoswipe.com/documentation/options.html), 18 | 19 | ``` 20 | defaultOpt: { 21 | fullscreenEl: true, 22 | shareEl: false, 23 | arrowEl: true, 24 | preloaderEl: true, 25 | loop: false, 26 | bgOpacity: 0.85, 27 | showHideOpacity: true, 28 | errorMsg: '图片加载失败', 29 | } 30 | ``` -------------------------------------------------------------------------------- /easy-test-vue/src/assets/styles/realize/lin-variables.scss: -------------------------------------------------------------------------------- 1 | $theme: #3963bc; 2 | 3 | /* 布局 */ 4 | $sidebar-width: 210px; 5 | $sidebar-background: #192a5e; 6 | 7 | $navbar-height: 30px; 8 | $navbar-padding: 20px; 9 | $header-height: 72px; 10 | 11 | $navbar-background: #BECCD8; 12 | $appmain-background: #F9FAFB; 13 | $header-background: #EEF4F9; 14 | $right-side-font-color: #666666; 15 | $reuse-tab-item-background: #E9EDF7; 16 | 17 | $title-color: #45526b; 18 | $parent-title-color: #3963bc; 19 | 20 | $table-border-color: #dee2e6; 21 | 22 | /* 菜单 */ 23 | $menuItem-hover: #0a1949; 24 | $menuItem-bg: #122150; 25 | $submenu-title: #c4c9d2; 26 | $menuItem-height: 50px; -------------------------------------------------------------------------------- /easy-test-flask/app/plugins/poem/app/forms.py: -------------------------------------------------------------------------------- 1 | from lin.forms import Form 2 | from wtforms import StringField, IntegerField 3 | from wtforms.validators import DataRequired, Optional, NumberRange 4 | 5 | 6 | class PoemListForm(Form): 7 | count = IntegerField() 8 | author = StringField(validators=[Optional()]) 9 | 10 | def validate_count(self, row): 11 | if not row.data: 12 | return True 13 | if int(row.data) > 100 or int(row.data) < 1: 14 | raise ValueError('必须在1~100之间取值') 15 | 16 | 17 | class PoemSearchForm(Form): 18 | q = StringField(validators=[ 19 | DataRequired(message='必须传入搜索关键字') 20 | ]) 21 | -------------------------------------------------------------------------------- /easy-test-vue/src/plugins/LinCmsUi/assets/style/container.scss: -------------------------------------------------------------------------------- 1 | .lin-wrap-ui /deep/ .el-card__body { 2 | padding-top: 30px; 3 | padding-bottom: 0px; 4 | } 5 | .lin-wrap-ui /deep/ .el-collapse { 6 | border-top: none; 7 | border-bottom: none; 8 | cursor: pointer; 9 | .el-collapse-item__header { 10 | border-bottom: none; 11 | color: #2f4e8c; 12 | padding-left: calc(100% - 77px); 13 | } 14 | 15 | .el-collapse-item__content { 16 | background: #e9f0f8; 17 | color: #2f4e8c; 18 | border-radius: 4px; 19 | padding: 0px 20px 20px 20px; 20 | margin-bottom: 20px; 21 | } 22 | } 23 | .lin-wrap-ui { 24 | padding: 30px 40px; 25 | } 26 | -------------------------------------------------------------------------------- /easy-test-flask/app/api/cms/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | register api to admin blueprint 3 | ~~~~~~~~~ 4 | :copyright: © 2019 by the Lin team. 5 | :license: MIT, see LICENSE for more details. 6 | """ 7 | 8 | from flask import Blueprint 9 | 10 | 11 | def create_cms(): 12 | cms = Blueprint('cms', __name__) 13 | from .admin import admin_api 14 | from .user import user_api 15 | from .log import log_api 16 | from .file import file_api 17 | from .test import test_api 18 | admin_api.register(cms) 19 | user_api.register(cms) 20 | log_api.register(cms) 21 | file_api.register(cms) 22 | test_api.register(cms) 23 | return cms 24 | -------------------------------------------------------------------------------- /easy-test-vue/src/plugins/LinCmsUi/README.md: -------------------------------------------------------------------------------- 1 | # 插件名: ui(LinCmsUi) 2 | 3 | 插件描述, ui 用于处于xxx业务场景, 提供了xxx功能 4 | 5 | ## 舞台视口列表 6 | 7 | ### TestView 8 | 9 | 地址: `/LinCmsUi/test.vue` 10 | 显示xxx内容, 可进行xxx操作 11 | 12 | ## 自由视口列表 13 | 14 | ### Test 15 | 16 | 属性: 17 | 18 | | Require | Name | Type | Default | Desc | 19 | |:-------:|:----:|:------:|:-------:|:----:| 20 | | true | name | String | val | 描述 | 21 | 22 | 事件: 23 | 24 | | Name | Argument | Desc | 25 | |:----:|:------------:|:----:| 26 | | name | 事件参数描述 | 描述 | 27 | 28 | 方法: 29 | 30 | | Name | Argument | Result | Desc | 31 | |:----:|:------------:|:----------:|:----:| 32 | | name | 方法参数描述 | 返回值描述 | 描述 | -------------------------------------------------------------------------------- /easy-test-vue/src/plugins/custom/views/Tinymce.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 富文本舞台页面 4 | 5 | 6 | 7 | 8 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /easy-test-vue/src/plugins/custom/README.md: -------------------------------------------------------------------------------- 1 | # 插件名: 图像上传组件示例(ImgsUpload) 2 | 3 | 插件描述, 图像上传组件示例 用于处于xxx业务场景, 提供了xxx功能 4 | 5 | ## 舞台视口列表 6 | 7 | ### TestView 8 | 9 | 地址: `/ImgsUpload/test.vue` 10 | 显示xxx内容, 可进行xxx操作 11 | 12 | ## 自由视口列表 13 | 14 | ### Test 15 | 16 | 属性: 17 | 18 | | Require | Name | Type | Default | Desc | 19 | |:-------:|:----:|:------:|:-------:|:----:| 20 | | true | name | String | val | 描述 | 21 | 22 | 事件: 23 | 24 | | Name | Argument | Desc | 25 | |:----:|:------------:|:----:| 26 | | name | 事件参数描述 | 描述 | 27 | 28 | 方法: 29 | 30 | | Name | Argument | Result | Desc | 31 | |:----:|:------------:|:----------:|:----:| 32 | | name | 方法参数描述 | 返回值描述 | 描述 | -------------------------------------------------------------------------------- /easy-test-flask/app/libs/job.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | from os import walk 4 | from os import path 5 | from app.libs.tasks import execute_test 6 | 7 | 8 | # 批量执行job 9 | def execute_job(pid, scheduler_id): 10 | execute_test.delay(pid, 0, scheduler_id) 11 | 12 | 13 | # 删除指定目录下某一类型的文件 14 | def delete_file(relative_project_path, reguler): 15 | 16 | file_dir = os.path.join(r'D:\pythonProgram\easy-test\easy-test-flask', relative_project_path) 17 | for parent, dirNames, fileNames in walk(file_dir): 18 | for fileName in fileNames: 19 | r = reguler 20 | res = re.search(r, fileName) 21 | os.remove(path.join(parent, fileName)) if res else 1 22 | -------------------------------------------------------------------------------- /easy-test-vue/script/template/plugin/README.md.ejs: -------------------------------------------------------------------------------- 1 | # 插件名: <%= title %>(<%= camelCaseName %>) 2 | 3 | 插件描述, <%= title %> 用于处于xxx业务场景, 提供了xxx功能 4 | 5 | ## 舞台视口列表 6 | 7 | ### TestView 8 | 9 | 地址: `/<%= camelCaseName %>/test.vue` 10 | 显示xxx内容, 可进行xxx操作 11 | 12 | ## 自由视口列表 13 | 14 | ### Test 15 | 16 | 属性: 17 | 18 | | Require | Name | Type | Default | Desc | 19 | |:-------:|:----:|:------:|:-------:|:----:| 20 | | true | name | String | val | 描述 | 21 | 22 | 事件: 23 | 24 | | Name | Argument | Desc | 25 | |:----:|:------------:|:----:| 26 | | name | 事件参数描述 | 描述 | 27 | 28 | 方法: 29 | 30 | | Name | Argument | Result | Desc | 31 | |:----:|:------------:|:----------:|:----:| 32 | | name | 方法参数描述 | 返回值描述 | 描述 | -------------------------------------------------------------------------------- /easy-test-mini/pages/case-detail/case-detail.wxss: -------------------------------------------------------------------------------- 1 | /* pages/case-detail/case-detail.wxss */ 2 | .detail { 3 | margin: 15rpx; 4 | width: 97%; 5 | } 6 | .name { 7 | font-size: 40rpx; 8 | margin: 0 0 30rpx 0; 9 | } 10 | .row { 11 | display: flex; 12 | width: 100%; 13 | } 14 | .colume { 15 | width: 50%; 16 | white-space:normal; 17 | word-break:break-all; 18 | word-wrap:break-word; 19 | margin: 5rpx; 20 | color: #3963bc; 21 | display: flex; 22 | align-items: center; 23 | } 24 | .colume-all { 25 | white-space:normal; 26 | word-break:break-all; 27 | word-wrap:break-word; 28 | margin: 5rpx; 29 | color: #3963bc; 30 | display: flex; 31 | align-items: center; 32 | } 33 | .text { 34 | color: #333333; 35 | } -------------------------------------------------------------------------------- /easy-test-vue/src/lin/plugins/auto-jump.js: -------------------------------------------------------------------------------- 1 | // 定时自动登出功能, 启用后一段时间无用户操作, 则自动登出. 需在项目 config 中配置 2 | import Vue from 'vue' 3 | import Config from '@/config' 4 | import store from '@/store' 5 | 6 | const Plugin = { 7 | install(vue) { 8 | // eslint-disable-next-line 9 | vue.prototype.$_lin_jump = () => { 10 | // eslint-disable-line 11 | if (!Config.openAutoJumpOut) { 12 | return 13 | } 14 | clearTimeout(this.timer) 15 | this.timer = setTimeout(() => { 16 | store.dispatch('loginOut') 17 | const { origin } = window.location 18 | window.location.href = origin 19 | }, Config.stagnateTime) 20 | } 21 | }, 22 | } 23 | 24 | Vue.use(Plugin) 25 | 26 | export default Plugin 27 | -------------------------------------------------------------------------------- /easy-test-flask/app/plugins/oss/app/oss.py: -------------------------------------------------------------------------------- 1 | import oss2 2 | 3 | from lin.util import get_random_str 4 | from lin.core import lin_config 5 | 6 | 7 | def upload_image_bytes(name: str, data: bytes): 8 | access_key_id = lin_config.get_config('oss.access_key_id') 9 | access_key_secret = lin_config.get_config('oss.access_key_secret') 10 | auth = oss2.Auth(access_key_id, access_key_secret) 11 | bucket = oss2.Bucket(auth, lin_config.get_config('oss.endpoint'), lin_config.get_config('oss.bucket_name')) 12 | suffix = name.split('.')[-1] 13 | rand_name = get_random_str(15) + '.' + suffix 14 | res = bucket.put_object(rand_name, data) 15 | if res.resp.status == 200: 16 | return res.resp.response.url 17 | return None 18 | -------------------------------------------------------------------------------- /easy-test-mini/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Eslint config file 3 | * Documentation: https://eslint.org/docs/user-guide/configuring/ 4 | * Install the Eslint extension before using this feature. 5 | */ 6 | module.exports = { 7 | env: { 8 | es6: true, 9 | browser: true, 10 | node: true, 11 | }, 12 | ecmaFeatures: { 13 | modules: true, 14 | }, 15 | parserOptions: { 16 | ecmaVersion: 2018, 17 | sourceType: 'module', 18 | }, 19 | globals: { 20 | wx: true, 21 | App: true, 22 | Page: true, 23 | getCurrentPages: true, 24 | getApp: true, 25 | Component: true, 26 | requirePlugin: true, 27 | requireMiniProgram: true, 28 | }, 29 | // extends: 'eslint:recommended', 30 | rules: {}, 31 | } 32 | -------------------------------------------------------------------------------- /easy-test-flask/app/api/v1/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | :copyright: © 2019 by the Lin team. 3 | :license: MIT, see LICENSE for more details. 4 | """ 5 | 6 | from flask import Blueprint 7 | from app.api.v1 import book, CaseGroup, case, project, task, scheduler, user, mock, overview 8 | 9 | 10 | def create_v1(): 11 | bp_v1 = Blueprint('v1', __name__) 12 | book.book_api.register(bp_v1) 13 | CaseGroup.case_group_api.register(bp_v1) 14 | case.case_api.register(bp_v1) 15 | project.project_api.register(bp_v1) 16 | task.task_api.register(bp_v1) 17 | scheduler.scheduler_api.register(bp_v1) 18 | user.user_api.register(bp_v1) 19 | mock.mock_api.register(bp_v1) 20 | overview.overview_api.register(bp_v1) 21 | return bp_v1 22 | -------------------------------------------------------------------------------- /easy-test-flask/app/libs/lin_response.py: -------------------------------------------------------------------------------- 1 | from flask import Response, jsonify 2 | 3 | 4 | class LinResponse(Response): 5 | # default_mimetype = 'application/json' # 设置默认 response 类型,默认是 text/html 6 | 7 | @classmethod 8 | def force_type(cls, rv, environ=None): 9 | """ 10 | 只有当视图函数返回 WSGI callable 或者其它可调用对象时,才会调用 force_type 11 | 具体参见 flask/app.py 中 make_response 源码部分 12 | :param rv: response value, a response object or wsgi application. 13 | :param environ: a WSGI environment object. 14 | :return: a response object. 15 | """ 16 | if isinstance(rv, (dict, list, tuple, set)): 17 | rv = jsonify(rv) 18 | return super(LinResponse, cls).force_type(rv, environ) 19 | -------------------------------------------------------------------------------- /easy-test-vue/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import VuexPersistence from 'vuex-persist' 4 | import mutations from './mutations' 5 | import state from './state' 6 | import * as getters from './getters' 7 | import actions from './actions' 8 | 9 | Vue.use(Vuex) 10 | 11 | const vuexLocal = new VuexPersistence({ 12 | storage: window.localStorage, 13 | reducer: stateData => ({ 14 | // eslint-disable-line 15 | logined: stateData.logined, 16 | user: stateData.user, 17 | auths: stateData.auths, 18 | }), 19 | }) 20 | 21 | export default new Vuex.Store({ 22 | state, 23 | getters, 24 | mutations, 25 | actions, 26 | plugins: [vuexLocal.plugin], 27 | strict: process.env.NODE_ENV !== 'production', 28 | }) 29 | -------------------------------------------------------------------------------- /easy-test-flask/app/validators/MockForm.py: -------------------------------------------------------------------------------- 1 | from lin.forms import Form 2 | from wtforms import StringField, IntegerField 3 | from wtforms.validators import DataRequired, length, Optional 4 | 5 | 6 | class MockForm(Form): 7 | url = StringField(validators=[DataRequired(message='请输入url')]) 8 | method = IntegerField(default=1) 9 | requestHeader = StringField(validators=[Optional()]) 10 | requestBody = StringField(validators=[Optional()]) 11 | responseHeader = StringField(validators=[Optional()]) 12 | responseBody = StringField(validators=[Optional()]) 13 | statusCode = IntegerField(default=200) 14 | msg = StringField(validators=[Optional()]) 15 | 16 | 17 | class MockSearchForm(Form): 18 | url = StringField() 19 | mid = IntegerField() 20 | -------------------------------------------------------------------------------- /easy-test-flask/app/plugins/poem/app/controller.py: -------------------------------------------------------------------------------- 1 | from flask import jsonify 2 | from lin.redprint import Redprint 3 | 4 | from app.plugins.poem.app.forms import PoemListForm, PoemSearchForm 5 | from .model import Poem 6 | 7 | api = Redprint('poem') 8 | 9 | 10 | @api.route('/all', methods=['GET']) 11 | def get_list(): 12 | form = PoemListForm().validate_for_api() 13 | poems = Poem().get_all(form) 14 | return jsonify(poems) 15 | 16 | 17 | @api.route('/search', methods=['GET']) 18 | def search(): 19 | form = PoemSearchForm().validate_for_api() 20 | poems = Poem().search(form.q.data) 21 | return jsonify(poems) 22 | 23 | 24 | @api.route('/authors', methods=['GET']) 25 | def get_authors(): 26 | authors = Poem.get_authors() 27 | return jsonify(authors) 28 | -------------------------------------------------------------------------------- /easy-test-vue/src/components/base/icon/lin-icon.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 29 | 37 | -------------------------------------------------------------------------------- /easy-test-vue/src/plugins/LinCmsUi/views/table/data.js: -------------------------------------------------------------------------------- 1 | export const tableColumn = [ 2 | // { prop: 'sorting', label: '排序', noRepeat: true }, 3 | { prop: 'rank', label: '排名' }, 4 | { prop: 'title', label: '电影名', width: 150 }, 5 | // { prop: 'originalTitle', label: '原名', width: 150 }, 6 | { 7 | prop: 'rating', 8 | label: '评分', 9 | noRepeat: true, 10 | width: 100, 11 | }, 12 | // { prop: 'genres', label: '类型', width: 150 }, 13 | { prop: 'directors', label: '导演', width: 150 }, 14 | { prop: 'casts', label: '主演', width: 150 }, 15 | { prop: 'year', label: '年份' }, 16 | { prop: 'recommend', label: '推荐', noRepeat: true }, 17 | { 18 | prop: 'remark', 19 | label: '备注', 20 | noRepeat: true, 21 | width: 200, 22 | }, 23 | ] 24 | 25 | export const a = 1 26 | -------------------------------------------------------------------------------- /easy-test-flask/app/libs/lin_flask.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify 2 | from app.libs.lin_response import LinResponse 3 | 4 | 5 | class LinFlask(Flask): 6 | response_class = LinResponse 7 | 8 | def make_response(self, rv, types: iter = (list, set)): 9 | """ 10 | 基本用途为将视图函数返回的值转换为flask内置支持的类型 11 | string, dict, tuple, Response instance, or WSGI callable 12 | 例如:默认将 list 和 set 类型直接转换为json类型返回, 13 | 这样就不需要在每个视图函数返回的时候都调用 jsonify 14 | 代码更加简洁 15 | 16 | 如果需要对视图函数返回的值进行统一的处理和封装,也可以在此函数下进行 17 | 18 | :param rv: response value 19 | :param types: types to change 20 | :return: 21 | """ 22 | if isinstance(rv, types): 23 | rv = jsonify(rv) 24 | return super(LinFlask, self).make_response(rv) 25 | -------------------------------------------------------------------------------- /easy-test-flask/app/libs/init.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from apscheduler.schedulers.background import BackgroundScheduler 4 | from flask_mail import Mail 5 | from flask_apscheduler import APScheduler 6 | from flask_celery import Celery 7 | from flask_pymongo import PyMongo 8 | from flask_socketio import SocketIO 9 | 10 | # 集成flask-pyMongo 11 | mongo = PyMongo() 12 | 13 | # 集成flask-socket.io 14 | socket_io = SocketIO() 15 | 16 | # 集成celery flask_celery 17 | # 启动worker: celery -A starter.celery worker -l info --pool=solo -f logs/celery.log 18 | celery = Celery() 19 | 20 | # 集成flask_apscheduler 21 | scheduler = APScheduler(BackgroundScheduler(timezone='Asia/Shanghai')) 22 | # apscheduler日志 23 | # logging.basicConfig() 24 | # logging.getLogger('apscheduler').setLevel(logging.DEBUG) 25 | 26 | 27 | mail = Mail() 28 | -------------------------------------------------------------------------------- /easy-test-vue/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: [ 3 | 'js', 4 | 'jsx', 5 | 'json', 6 | 'vue' 7 | ], 8 | transform: { 9 | '^.+\\.vue$': 'vue-jest', 10 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 11 | '^.+\\.jsx?$': 'babel-jest' 12 | }, 13 | transformIgnorePatterns: [ 14 | '/node_modules/' 15 | ], 16 | moduleNameMapper: { 17 | '^@/(.*)$': '/src/$1' 18 | }, 19 | snapshotSerializers: [ 20 | 'jest-serializer-vue' 21 | ], 22 | testMatch: [ 23 | '**/tests/unit/**/*.test.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 24 | ], 25 | testURL: 'http://localhost/', 26 | watchPlugins: [ 27 | 'jest-watch-typeahead/filename', 28 | 'jest-watch-typeahead/testname' 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /easy-test-flask/add_super.py: -------------------------------------------------------------------------------- 1 | """ 2 | :copyright: © 2019 by the Lin team. 3 | :license: MIT, see LICENSE for more details. 4 | """ 5 | 6 | from lin.core import User 7 | from lin.db import db 8 | 9 | from app.app import create_app 10 | 11 | 12 | def main(): 13 | app = create_app() 14 | with app.app_context(): 15 | with db.auto_commit(): 16 | # 创建一个超级管理员 17 | user = User() 18 | user.username = 'super' 19 | user.password = '123456' 20 | user.email = '1234995678@qq.com' 21 | # admin 2 的时候为超级管理员,普通用户为 1 22 | user.admin = 2 23 | db.session.add(user) 24 | 25 | 26 | if __name__ == '__main__': 27 | try: 28 | main() 29 | print("新增超级管理员成功") 30 | except Exception as e: 31 | raise e 32 | -------------------------------------------------------------------------------- /easy-test-flask/requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2019.3.9 2 | chardet==3.0.4 3 | Click==7.0 4 | cymysql==0.9.13 5 | Flask==1.0.2 6 | Flask-Cors==2.1.0 7 | Flask-JWT-Extended==3.12.1 8 | Flask-SQLAlchemy==2.3.2 9 | Flask-WTF==0.14.2 10 | idna==2.6 11 | itsdangerous==1.1.0 12 | Jinja2==2.10 13 | Lin-CMS==0.2.0b2 14 | MarkupSafe==1.1.1 15 | pipfile==0.0.2 16 | PyJWT==1.7.1 17 | requests==2.18.4 18 | six==1.12.0 19 | SQLAlchemy==1.2.11 20 | toml==0.10.0 21 | urllib3==1.22 22 | Werkzeug==0.14.1 23 | WTForms==2.2.1 24 | oss2==2.6.1 25 | pypinyin==0.37.0 26 | pymongo==3.10.1 27 | Flask-PyMongo==2.3.0 28 | flask-socketio==3.0.2 29 | python-engineio==3.13.2 30 | python-socketio==4.2.0 31 | celery==4.4.2 32 | flask-celery-helper==1.1.0 33 | flask-apscheduler==1.11.0 34 | flask-mail==0.9.1 35 | xlrd==1.2.0 36 | xlutils==2.0.0 37 | flower==0.9.5 38 | -------------------------------------------------------------------------------- /easy-test-vue/script/lib/semver-validate.js: -------------------------------------------------------------------------------- 1 | // 预计算一下版本是否有冲突 2 | const semver = require('semver') 3 | 4 | const validateSemver = (range1, range2) => { 5 | if (!range1 || !range2) { 6 | return false 7 | } 8 | // 都是指定版本 9 | if (semver.valid(range1) && semver.valid(range2)) { 10 | return (semver.coerce(range1) === semver.coerce(range2)) 11 | } 12 | 13 | // 都是范围 14 | if (semver.validRange(range1) && semver.validRange(range2)) { 15 | return semver.intersects(range1, range2) 16 | } 17 | 18 | // 一个版本一个范围 19 | if (semver.valid(range1) && semver.validRange(range2)) { 20 | return semver.satisfies(range1, range2) 21 | } 22 | 23 | if (semver.valid(range2) && semver.validRange(range1)) { 24 | return semver.satisfies(range2, range1) 25 | } 26 | 27 | return false 28 | } 29 | 30 | module.exports = validateSemver 31 | -------------------------------------------------------------------------------- /easy-test-vue/script/plugin-get-config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | const path = require('path') 3 | const chalk = require('chalk') 4 | const ejs = require('ejs') 5 | const getAllPlugin = require('./lib/plugin-get-all') 6 | 7 | const targetDir = path.resolve(__dirname, '../src/config/stage/plugins.js') 8 | const pluginsPath = path.resolve(__dirname, '../src/plugins') 9 | const templatePath = path.resolve(__dirname, './template/plugin-stage-config.js.ejs') 10 | 11 | // eslint-disable-next-line 12 | console.log(chalk.green('配置插件...')); 13 | 14 | const template = fs.readFileSync(templatePath, 'utf8') 15 | const puginList = getAllPlugin(pluginsPath) 16 | const result = ejs.render(template, { plugins: puginList }) 17 | 18 | fs.writeFile(targetDir, result) 19 | 20 | // eslint-disable-next-line 21 | console.log(chalk.green(`插件配置完成: ${targetDir}\n`)) 22 | -------------------------------------------------------------------------------- /easy-test-vue/src/config/stage/book.js: -------------------------------------------------------------------------------- 1 | const bookRouter = { 2 | route: null, 3 | name: null, 4 | title: '图书管理', 5 | type: 'folder', // 类型: folder, tab, view 6 | icon: 'iconfont icon-tushuguanli', 7 | filePath: 'views/book/', // 文件路径 8 | order: null, 9 | inNav: true, 10 | permission: ['查看lin的信息'], 11 | children: [ 12 | { 13 | title: '添加图书', 14 | type: 'view', 15 | name: 'bookAdd', 16 | route: '/book/add', 17 | filePath: 'views/book/BookAdd.vue', 18 | inNav: true, 19 | icon: 'iconfont icon-tushuguanli', 20 | }, 21 | { 22 | title: '图书列表', 23 | type: 'view', 24 | name: 'bookAdd', 25 | route: '/book/list', 26 | filePath: 'views/book/BookList.vue', 27 | inNav: true, 28 | icon: 'iconfont icon-tushuguanli', 29 | }, 30 | ], 31 | } 32 | 33 | export default bookRouter 34 | -------------------------------------------------------------------------------- /easy-test-vue/src/lin/utils/token.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 存储tokens 3 | * @param {string} accessToken 4 | * @param {string} refreshToken 5 | */ 6 | export function saveTokens(accessToken, refreshToken) { 7 | localStorage.setItem('access_token', `Bearer ${accessToken}`) 8 | localStorage.setItem('refresh_token', `Bearer ${refreshToken}`) 9 | } 10 | 11 | /** 12 | * 存储access_token 13 | * @param {string} accessToken 14 | */ 15 | export function saveAccessToken(accessToken) { 16 | localStorage.setItem('access_token', `Bearer ${accessToken}`) 17 | } 18 | 19 | /** 20 | * 获得某个token 21 | * @param {string} tokenKey 22 | */ 23 | export function getToken(tokenKey) { 24 | return localStorage.getItem(tokenKey) 25 | } 26 | 27 | /** 28 | * 移除token 29 | */ 30 | export function removeToken() { 31 | localStorage.removeItem('access_token') 32 | localStorage.removeItem('refresh_token') 33 | } 34 | -------------------------------------------------------------------------------- /easy-test-vue/public/tinymce/skins/content/default/content.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table td,table th{border:1px solid #ccc;padding:.4rem}blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem} 8 | /*# sourceMappingURL=content.min.css.map */ 9 | -------------------------------------------------------------------------------- /easy-test-vue/script/template/plugin/views/Stage2.vue.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 插件<%= camelCaseName %>舞台页面 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 35 | 36 | 38 | -------------------------------------------------------------------------------- /easy-test-flask/app/models/user.py: -------------------------------------------------------------------------------- 1 | """ 2 | @Time : 2020/2/23 17:08 3 | @Author : 郭家兴 4 | @Email : 302802003@qq.com 5 | @File : user.py.py 6 | @Desc : 7 | """ 8 | from lin import db 9 | from lin.core import User as _User 10 | from sqlalchemy import Column, String 11 | 12 | 13 | class User(_User): 14 | # 扩展user 15 | phone = Column(String(20), unique=True, comment='手机号') 16 | openid = Column(String(255), unique=True, comment='微信openid') 17 | 18 | @classmethod 19 | def execute_top(cls): 20 | # 执行测试次数top3 21 | 22 | execute_top = db.session.execute("SELECT lin_user.username,count( * ) AS count FROM `task`,`lin_user` WHERE " 23 | "lin_user.id = task.create_user AND task.delete_time IS NULL AND " 24 | "create_user > 0 GROUP BY create_user ORDER BY count( * ) DESC LIMIT 3") 25 | 26 | return list(execute_top) 27 | 28 | -------------------------------------------------------------------------------- /easy-test-vue/public/tinymce/skins/content/writer/content.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem auto;max-width:900px}table{border-collapse:collapse}table td,table th{border:1px solid #ccc;padding:.4rem}blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem} 8 | /*# sourceMappingURL=content.min.css.map */ 9 | -------------------------------------------------------------------------------- /easy-test-vue/src/components/layout/AppMain.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 28 | 29 | 38 | -------------------------------------------------------------------------------- /easy-test-vue/src/views/error-page/404.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 17 | 18 | 37 | -------------------------------------------------------------------------------- /easy-test-vue/src/config/stage/scheduler.js: -------------------------------------------------------------------------------- 1 | const schedulerRouter = { 2 | route: null, 3 | name: null, 4 | title: '定时任务', 5 | type: 'tab', 6 | icon: 'iconfont icon-corn', 7 | filePath: 'views/scheduler/', 8 | order: 6, 9 | inNav: true, 10 | permission: ['定时任务列表'], 11 | children: [ 12 | { 13 | route: '/scheduler/list', 14 | type: 'view', 15 | name: 'schedulerList', 16 | inNav: true, 17 | filePath: 'views/scheduler/SchedulerList.vue', 18 | title: '任务列表', 19 | icon: 'iconfont icon-corn', 20 | keepAlive: true, 21 | }, 22 | { 23 | route: '/scheduler/add', 24 | type: 'view', 25 | name: 'schedulerAdd', 26 | filePath: 'views/scheduler/SchedulerAdd.vue', 27 | inNav: true, 28 | title: '添加任务', 29 | icon: 'iconfont icon-add', 30 | keepAlive: false, 31 | permission: ['新增定时任务'], 32 | }, 33 | ], 34 | } 35 | 36 | export default schedulerRouter 37 | -------------------------------------------------------------------------------- /easy-test-mini/project.private.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "setting": {}, 3 | "condition": { 4 | "miniprogram": { 5 | "list": [ 6 | { 7 | "name": "", 8 | "pathName": "pages/home/home", 9 | "query": "", 10 | "scene": null 11 | }, 12 | { 13 | "name": "", 14 | "pathName": "pages/index/index", 15 | "query": "", 16 | "scene": null 17 | }, 18 | { 19 | "name": "", 20 | "pathName": "pages/case/case", 21 | "query": "", 22 | "scene": null 23 | }, 24 | { 25 | "name": "", 26 | "pathName": "pages/case-detail/case-detail", 27 | "query": "", 28 | "scene": null 29 | } 30 | ] 31 | } 32 | }, 33 | "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html" 34 | } -------------------------------------------------------------------------------- /easy-test-vue/src/plugins/LinCmsUi/components/Component.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 组件: LinCmsUitest 4 | 5 | 6 | 7 | 8 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /easy-test-vue/public/tinymce/skins/content/document/content.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | @media screen{html{background:#f4f4f4}}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif}@media screen{body{background-color:#fff;box-shadow:0 0 4px rgba(0,0,0,.15);box-sizing:border-box;margin:1rem auto 0;max-width:820px;min-height:calc(99vh);padding:4rem 6rem 6rem 6rem}}table{border-collapse:collapse}table td,table th{border:1px solid #ccc;padding:.4rem}blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}figure figcaption{color:#999;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0} 8 | /*# sourceMappingURL=content.min.css.map */ 9 | -------------------------------------------------------------------------------- /easy-test-mini/pages/case/case.wxss: -------------------------------------------------------------------------------- 1 | /* pages/case/case.wxss */ 2 | .header{ 3 | display: flex; 4 | flex-direction: row; 5 | width: 100%; 6 | padding: 15rpx 10rpx; 7 | position: fixed; 8 | top: 0; 9 | height: 75rpx; 10 | z-index: 999999999; 11 | background-color: white; 12 | } 13 | .search { 14 | width: 100%; 15 | margin: auto; 16 | } 17 | .body{ 18 | /* height: 3000rpx; */ 19 | margin-top: 90rpx; 20 | } 21 | .popup{ 22 | background-color: white; 23 | height: 100%; 24 | width: 500rpx; 25 | border-radius: 0 4% 4% 0; 26 | padding-top: 20rpx; 27 | } 28 | .list { 29 | height: 100%; 30 | } 31 | .list-content-select{ 32 | color: #2c61b4; 33 | font-size: 37rpx; 34 | } 35 | .list-content{ 36 | font-size: 37rpx; 37 | } 38 | .url { 39 | color: #adb9d3; 40 | font-size: 24rpx; 41 | } 42 | .title { 43 | display: flex; 44 | flex-direction: row; 45 | align-items: center; 46 | } 47 | .method { 48 | margin-left: 10rpx; 49 | } 50 | .title-name { 51 | font-size: 36rpx; 52 | } 53 | -------------------------------------------------------------------------------- /easy-test-vue/script/template/plugin/stage-config.js.ejs: -------------------------------------------------------------------------------- 1 | const <%= camelCaseName %>Router = { 2 | route: null, 3 | name: null, 4 | title: '<%= title %>', 5 | type: 'folder', 6 | icon: 'iconfont icon-demo', 7 | filePath: 'views/<%= camelCaseName %>/', 8 | order: null, 9 | inNav: true, 10 | children: [ 11 | { 12 | title: '舞台页面', 13 | type: 'view', 14 | name: '<%= camelCaseName %>Stage1', 15 | route: '/<%= name %>/stage1', 16 | filePath: 'plugins/<%= camelCaseName %>/views/Stage1.vue', 17 | inNav: true, 18 | icon: 'iconfont icon-demo', 19 | right: null, 20 | }, 21 | { 22 | title: '舞台页面', 23 | type: 'view', 24 | name: '<%= camelCaseName %>Stage2', 25 | route: '/<%= name %>/stage2', 26 | filePath: 'plugins/<%= camelCaseName %>/views/Stage2.vue', 27 | inNav: true, 28 | icon: 'iconfont icon-demo', 29 | right: null, 30 | }, 31 | ], 32 | } 33 | 34 | export default <%= camelCaseName %>Router 35 | -------------------------------------------------------------------------------- /easy-test-vue/src/lin/utils/cookie.js: -------------------------------------------------------------------------------- 1 | import cookies from 'js-cookie' 2 | /** 3 | * 存储tokens 4 | * @param {string} accessToken 5 | * @param {string} refreshToken 6 | */ 7 | export function saveTokens(accessToken, refreshToken) { 8 | // 存储tokens tokens只进入cookies,不进入vuex全局管理 9 | cookies.set('access_token', `Bearer ${accessToken}`) 10 | cookies.set('refresh_token', `Bearer ${refreshToken}`) 11 | } 12 | 13 | /** 14 | * 存储access_token 15 | * @param {string} accessToken 16 | */ 17 | export function saveAccessToken(accessToken) { 18 | cookies.set('access_token', `Bearer ${accessToken}`) 19 | } 20 | 21 | /** 22 | * 获得某个token 23 | * @param {string} tokenKey 24 | */ 25 | export function getToken(tokenKey) { 26 | return cookies.get(tokenKey) 27 | } 28 | 29 | /** 30 | * 移除token 31 | */ 32 | export function removeToken() { 33 | cookies.remove('access_token') 34 | cookies.remove('refresh_token') 35 | sessionStorage.removeItem('flag') 36 | sessionStorage.clear() 37 | localStorage.clear() 38 | } 39 | -------------------------------------------------------------------------------- /easy-test-vue/src/components/base/dropdown/lin-dropdown.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ title }} 5 | 6 | {{ single }} 7 | 8 | 9 | 10 | 11 | 12 | 33 | 34 | 40 | -------------------------------------------------------------------------------- /easy-test-vue/src/models/book.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable class-methods-use-this */ 2 | import { post, get, put, _delete } from '@/lin/plugins/axios' 3 | 4 | // 我们通过 class 这样的语法糖使模型这个概念更加具象化,其优点:耦合性低、可维护性。 5 | class Book { 6 | // constructor() {} 7 | 8 | // 类中的方法可以代表一个用户行为 9 | async addBook(info) { 10 | const res = await post('v1/book', info, { handleError: true }) 11 | return res 12 | } 13 | 14 | // 在这里通过 async await 语法糖让代码同步执行 15 | // 1. await 一定要搭配 async 来使用 16 | // 2. await 后面跟的是一个 Promise 对象 17 | async getBook(id) { 18 | const res = await get(`v1/book/${id}`) 19 | return res 20 | } 21 | 22 | async editBook(id, info) { 23 | const res = await put(`v1/book/${id}`, info) 24 | return res 25 | } 26 | 27 | async delectBook(id) { 28 | const res = await _delete(`v1/book/${id}`) 29 | return res 30 | } 31 | 32 | async getBooks() { 33 | const res = await get('v1/book', { handleError: true }) 34 | return res 35 | } 36 | } 37 | 38 | export default new Book() 39 | -------------------------------------------------------------------------------- /easy-test-vue/script/template/plugin/components/Component.vue.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 组件: <%= camelCaseName %>test 4 | 5 | 6 | 7 | 8 | 39 | 40 | 42 | -------------------------------------------------------------------------------- /easy-test-flask/tests/test_user.py: -------------------------------------------------------------------------------- 1 | """ 2 | :copyright: © 2019 by the Lin team. 3 | :license: MIT, see LICENSE for more details. 4 | """ 5 | from app.app import create_app 6 | from tests.utils import write_token, get_token 7 | 8 | app = create_app() 9 | 10 | 11 | def test_login(): 12 | with app.test_client() as c: 13 | rv = c.post('/cms/user/login', json={ 14 | 'nickname': 'super', 'password': '123456' 15 | }) 16 | json_data = rv.get_json() 17 | print(json_data) 18 | write_token(json_data) 19 | assert json_data['access_token'] is not None 20 | assert rv.status_code == 200 21 | 22 | 23 | def test_change_password(): 24 | with app.test_client() as c: 25 | rv = c.put('/cms/user/', headers={ 26 | 'Authorization': 'Bearer ' + get_token() 27 | }, json={ 28 | 'email': '1312342604@qq.com' 29 | }) 30 | json_data = rv.get_json() 31 | print(json_data) 32 | assert rv.status_code == 201 33 | -------------------------------------------------------------------------------- /easy-test-mini/pages/test/test.js: -------------------------------------------------------------------------------- 1 | // pages/test/test.js 2 | 3 | import io from 'weapp.socket.io'; 4 | // const socket = io('http://127.0.0.1:5000'); 5 | 6 | Page({ 7 | 8 | /** 9 | * 页面的初始数据 10 | */ 11 | data: { 12 | }, 13 | 14 | /** 15 | * 生命周期函数--监听页面加载 16 | */ 17 | onLoad: function (options) { 18 | 19 | }, 20 | 21 | /** 22 | * 生命周期函数--监听页面初次渲染完成 23 | */ 24 | onReady: function () { 25 | 26 | }, 27 | 28 | /** 29 | * 生命周期函数--监听页面显示 30 | */ 31 | onShow: function () { 32 | 33 | }, 34 | 35 | /** 36 | * 生命周期函数--监听页面隐藏 37 | */ 38 | onHide: function () { 39 | 40 | }, 41 | 42 | /** 43 | * 生命周期函数--监听页面卸载 44 | */ 45 | onUnload: function () { 46 | 47 | }, 48 | 49 | /** 50 | * 页面相关事件处理函数--监听用户下拉动作 51 | */ 52 | onPullDownRefresh: function () { 53 | 54 | }, 55 | 56 | /** 57 | * 页面上拉触底事件的处理函数 58 | */ 59 | onReachBottom: function () { 60 | 61 | }, 62 | 63 | /** 64 | * 用户点击右上角分享 65 | */ 66 | onShareAppMessage: function () { 67 | 68 | } 69 | }) -------------------------------------------------------------------------------- /easy-test-flask/app/validators/MineForm.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from lin.forms import Form 4 | from wtforms import StringField, FieldList, IntegerField, DateTimeField, Field 5 | from wtforms.validators import DataRequired, length, Optional 6 | 7 | 8 | class MineSearchForm(Form): 9 | uid = IntegerField() 10 | # project | case | scheduler name 11 | name = StringField(length(max=20, message='描述文字长度应小于30个字')) 12 | page = IntegerField(default=1) 13 | count = IntegerField(default=10) 14 | start = DateTimeField(validators=[]) 15 | end = DateTimeField(validators=[]) 16 | 17 | def validate_start(self, value): 18 | if value.data: 19 | try: 20 | _ = time.strptime(value.data, '%Y-%m-%d %H:%M:%S') 21 | except ValueError as e: 22 | raise e 23 | 24 | def validate_end(self, value): 25 | if value.data: 26 | try: 27 | _ = time.strptime(value.data, '%Y-%m-%d %H:%M:%S') 28 | except ValueError as e: 29 | raise e 30 | -------------------------------------------------------------------------------- /easy-test-flask/app/validators/TaskForm.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from lin.forms import Form 4 | from wtforms import StringField, IntegerField, DateTimeField, Field 5 | from wtforms.validators import DataRequired, length, Optional 6 | 7 | 8 | class TaskSearchForm(Form): 9 | project = IntegerField(validators=[Optional()]) 10 | no = StringField(validators=[Optional()]) 11 | user = IntegerField(validators=[Optional()]) 12 | page = IntegerField(validators=[Optional()]) 13 | count = IntegerField(validators=[Optional()]) 14 | start = DateTimeField(validators=[]) 15 | end = DateTimeField(validators=[]) 16 | 17 | def validate_start(self, value): 18 | if value.data: 19 | try: 20 | _ = time.strptime(value.data, '%Y-%m-%d %H:%M:%S') 21 | except ValueError as e: 22 | raise e 23 | 24 | def validate_end(self, value): 25 | if value.data: 26 | try: 27 | _ = time.strptime(value.data, '%Y-%m-%d %H:%M:%S') 28 | except ValueError as e: 29 | raise e 30 | -------------------------------------------------------------------------------- /easy-test-mini/pages/home/home.wxss: -------------------------------------------------------------------------------- 1 | /* pages/home/home.wxss */ 2 | .banner{ 3 | width: 100%; 4 | height: 300rpx; 5 | } 6 | .num{ 7 | color: #4577ff; 8 | } 9 | .text{ 10 | color: #666666; 11 | } 12 | .grid{ 13 | box-shadow:0 4rpx 20rpx 0 rgba(212,217,223,.5); 14 | border-radius:6px; 15 | margin: 5rpx; 16 | } 17 | .grid-echarts{ 18 | box-shadow:0 4rpx 20rpx 0 rgba(212,217,223,.5); 19 | border-radius:6px; 20 | margin: 5rpx; 21 | padding: 10rpx; 22 | } 23 | .project-btn{ 24 | margin-top: 20rpx; 25 | } 26 | .popup{ 27 | background-color: white; 28 | height: 1000rpx; 29 | border-radius: 2% 2% 0 0; 30 | padding-top: 20rpx; 31 | } 32 | .list{ 33 | height: 100%; 34 | } 35 | .list-content-select{ 36 | color: #2c61b4; 37 | font-size: 32rpx; 38 | } 39 | .list-content{ 40 | font-size: 32rpx; 41 | } 42 | 43 | .container { 44 | padding: 0; 45 | display: flex; 46 | flex-direction: row; 47 | justify-content: center; 48 | } 49 | .data-green { 50 | color: #00c292; 51 | font-size: 38rpx; 52 | } 53 | .data-red { 54 | color: #F4516C; 55 | font-size: 38rpx; 56 | } -------------------------------------------------------------------------------- /easy-test-flask/app/models/UserAuth.py: -------------------------------------------------------------------------------- 1 | """ 2 | @Time : 2020/3/14 15:12 3 | @Author : 郭家兴 4 | @Email : 302802003@qq.com 5 | @File : UserAuth.py 6 | @Desc : 用户权限分配表 对用户可操作的测试分组、测试项目授权 7 | """ 8 | from lin.interface import InfoCrud as Base 9 | from sqlalchemy import Column, SmallInteger, Integer 10 | 11 | from app.libs.enums import UserAuthEnum 12 | 13 | 14 | class UserAuth(Base): 15 | id = Column(Integer, primary_key=True, autoincrement=True) 16 | user_id = Column(Integer, nullable=False, comment='用户id') 17 | auth_id = Column(Integer, nullable=False, comment='权限id') 18 | _type = Column('type', SmallInteger, nullable=False,comment='权限id类型 ; 1 -> group分组 | 2 -> project工程') 19 | 20 | @property 21 | def type(self): 22 | return UserAuthEnum(self._type) 23 | 24 | @type.setter 25 | def type(self,user_auth): 26 | self._type = user_auth.value 27 | 28 | @classmethod 29 | def get_user_auth(cls,user_id, auth_id, type): 30 | auth = UserAuth.query.filter().filter_by(user_id=user_id,auth_id=auth_id,_type=type).first() 31 | return auth -------------------------------------------------------------------------------- /easy-test-vue/src/views/admin/user/UserAdd.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 新建用户 4 | 5 | 6 | 7 | 8 | 33 | 34 | 51 | -------------------------------------------------------------------------------- /easy-test-vue/src/router/home-router.js: -------------------------------------------------------------------------------- 1 | import stageConfig from '@/config/stage' // 引入舞台配置 2 | 3 | // 深度遍历配置树, 摘取叶子节点作为路由部分 4 | function deepTravel(config, fuc) { 5 | if (Array.isArray(config)) { 6 | config.forEach(subConfig => { 7 | deepTravel(subConfig, fuc) 8 | }) 9 | } else if (config.children) { 10 | config.children.forEach(subConfig => { 11 | deepTravel(subConfig, fuc) 12 | }) 13 | } else { 14 | fuc(config) 15 | } 16 | } 17 | 18 | const homeRouter = [] 19 | 20 | deepTravel(stageConfig, viewConfig => { 21 | // 构造舞台view路由 22 | const viewRouter = {} 23 | viewRouter.path = viewConfig.route 24 | viewRouter.name = viewConfig.name 25 | viewRouter.component = () => import(`@/${viewConfig.filePath}`) 26 | viewRouter.meta = { 27 | title: viewConfig.title, 28 | icon: viewConfig.icon, 29 | right: viewConfig.right, 30 | type: viewConfig.type, 31 | blueBaseColor: viewConfig.blueBaseColor ? 'viewConfig.blueBaseColor' : '', 32 | keepAlive: viewConfig.keepAlive, 33 | } 34 | homeRouter.push(viewRouter) 35 | }) 36 | 37 | export default homeRouter 38 | -------------------------------------------------------------------------------- /easy-test-vue/vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | function resolve(dir) { 4 | return path.join(__dirname, dir) 5 | } 6 | 7 | module.exports = { 8 | lintOnSave: true, 9 | productionSourceMap: false, 10 | // assetsDir: 'static', 11 | chainWebpack: (config) => { 12 | config.resolve.alias 13 | .set('@', resolve('src')) 14 | .set('lin', resolve('src/lin')) 15 | .set('assets', resolve('src/assets')) 16 | config.module 17 | .rule('md') 18 | .test(/\.md$/) 19 | .use('vue-loader') 20 | .loader('vue-loader') 21 | .end() 22 | .use("vue-markdown-loader") 23 | .loader('vue-markdown-loader/lib/markdown-compiler') 24 | }, 25 | configureWebpack: { 26 | resolve: { 27 | extensions: ['.js', '.json', '.vue', '.scss', '.html'], 28 | }, 29 | }, 30 | css: { 31 | loaderOptions: { 32 | sass: { 33 | data: '@import "@/assets/styles/share.scss";', 34 | }, 35 | }, 36 | }, 37 | devServer: {}, 38 | // node_modules依赖项es6语法未转换问题 39 | transpileDependencies: [ 40 | 'vuex-persist', 41 | ], 42 | } 43 | -------------------------------------------------------------------------------- /easy-test-vue/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Lin 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. -------------------------------------------------------------------------------- /easy-test-flask/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 TaleLin 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. -------------------------------------------------------------------------------- /easy-test-vue/src/lin/models/notify.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable class-methods-use-this */ 2 | import { post, get, put } from '@/lin/plugins/axios' 3 | import Config from '../../config' 4 | import Sse from '../utils/sse' 5 | 6 | export default class Notify { 7 | url = null 8 | 9 | events = null 10 | 11 | sse = null 12 | 13 | constructor(url) { 14 | this.url = url 15 | } 16 | 17 | async getEvents() { 18 | const res = await get('cms/notify/events') 19 | this.events = res.events 20 | } 21 | 22 | async initSse() { 23 | await this.getEvents() 24 | this.sse = new Sse(Config.baseUrl + this.url, this.events) 25 | } 26 | 27 | /** 28 | * 创建events 29 | * @param {number} group_id 30 | * @param {Array} events 31 | */ 32 | async createEvents(group_id, events) { 33 | const res = await post('cms/notify/events', { group_id, events }) 34 | return res 35 | } 36 | 37 | /** 38 | * 更新events 39 | * @param {number} group_id 40 | * @param {Array} events 41 | */ 42 | async updateEvents(group_id, events) { 43 | const res = await put('cms/notify/events', { group_id, events }) 44 | return res 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /easy-test-vue/public/tinymce/skins/content/default/content.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | body { 8 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 9 | line-height: 1.4; 10 | margin: 1rem; 11 | } 12 | table { 13 | border-collapse: collapse; 14 | } 15 | table th, 16 | table td { 17 | border: 1px solid #ccc; 18 | padding: .4rem; 19 | } 20 | blockquote { 21 | border-left: 2px solid #ccc; 22 | margin-left: 1.5rem; 23 | padding-left: 1rem; 24 | } 25 | figure { 26 | display: table; 27 | margin: 1rem auto; 28 | } 29 | figure figcaption { 30 | color: #999; 31 | display: block; 32 | margin-top: .25rem; 33 | text-align: center; 34 | } 35 | hr { 36 | border-color: #ccc; 37 | border-style: solid; 38 | border-width: 1px 0 0 0; 39 | } 40 | code { 41 | background-color: #e8e8e8; 42 | border-radius: 3px; 43 | padding: .1rem .2rem; 44 | } -------------------------------------------------------------------------------- /easy-test-flask/tests/test_admin.py: -------------------------------------------------------------------------------- 1 | """ 2 | :copyright: © 2019 by the Lin team. 3 | :license: MIT, see LICENSE for more details. 4 | """ 5 | from app.app import create_app 6 | from tests.utils import get_token 7 | 8 | app = create_app() 9 | 10 | 11 | def test_authority(): 12 | with app.test_client() as c: 13 | rv = c.get('/cms/admin/authority', headers={ 14 | 'Authorization': 'Bearer ' + get_token() 15 | }) 16 | json_data = rv.get_json() 17 | print(json_data) 18 | assert rv.status_code == 200 19 | 20 | 21 | def test_delete_user(): 22 | with app.test_client() as c: 23 | rv = c.delete('/cms/admin/6', headers={ 24 | 'Authorization': 'Bearer ' + get_token() 25 | }) 26 | json_data = rv.get_json() 27 | print(json_data) 28 | assert rv.status_code == 201 29 | 30 | 31 | def test_get_admin_users(): 32 | with app.test_client() as c: 33 | rv = c.get('/cms/admin/users', headers={ 34 | 'Authorization': 'Bearer ' + get_token() 35 | }) 36 | json_data = rv.get_json() 37 | print(len(json_data['collection'])) 38 | assert rv.status_code == 200 39 | -------------------------------------------------------------------------------- /easy-test-vue/src/components/base/source-code/source-code.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 本页源码 4 | 5 | 6 | 7 | 22 | 23 | 49 | -------------------------------------------------------------------------------- /easy-test-vue/default.conf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | server { 5 | listen 443; 6 | server_name www.xxx.com; 7 | ssl on; 8 | ssl_certificate /home/www.xxx.com.pem; 9 | ssl_certificate_key /home/www.xxx.com.key; 10 | ssl_session_timeout 5m; 11 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 12 | ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP; 13 | ssl_prefer_server_ciphers on; 14 | 15 | location / { 16 | root /usr/share/nginx/html; 17 | index index.html index.htm; 18 | } 19 | 20 | 21 | location ~ ^/v1/* { 22 | proxy_pass http://api:5000; 23 | proxy_read_timeout 3000; 24 | } 25 | 26 | location ~ ^/cms/* { 27 | proxy_pass http://api:5000; 28 | proxy_read_timeout 3000; 29 | } 30 | 31 | location ~ ^/mock/* { 32 | proxy_pass http://api:5000; 33 | proxy_read_timeout 3000; 34 | } 35 | 36 | location ~ ^/assets/* { 37 | proxy_pass http://api:5000; 38 | proxy_read_timeout 3000; 39 | } 40 | 41 | location ~ ^/socket.io/* { 42 | proxy_pass http://api:5000; 43 | } 44 | 45 | } 46 | 47 | server { 48 | listen 80; 49 | server_name www.xxx.com; 50 | rewrite ^(.*)$ https://$host$1 permanent; #将所有http请求通过rewrite重定向到https。 51 | } 52 | 53 | -------------------------------------------------------------------------------- /easy-test-vue/public/tinymce/skins/content/writer/content.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | body { 8 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 9 | line-height: 1.4; 10 | margin: 1rem auto; 11 | max-width: 900px; 12 | } 13 | table { 14 | border-collapse: collapse; 15 | } 16 | table th, 17 | table td { 18 | border: 1px solid #ccc; 19 | padding: .4rem; 20 | } 21 | blockquote { 22 | border-left: 2px solid #ccc; 23 | margin-left: 1.5rem; 24 | padding-left: 1rem; 25 | } 26 | figure { 27 | display: table; 28 | margin: 1rem auto; 29 | } 30 | figure figcaption { 31 | color: #999; 32 | display: block; 33 | margin-top: .25rem; 34 | text-align: center; 35 | } 36 | hr { 37 | border-color: #ccc; 38 | border-style: solid; 39 | border-width: 1px 0 0 0; 40 | } 41 | code { 42 | background-color: #e8e8e8; 43 | border-radius: 3px; 44 | padding: .1rem .2rem; 45 | } -------------------------------------------------------------------------------- /easy-test-flask/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.tuna.tsinghua.edu.cn/simple/" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | cymysql = "==0.9.13" 8 | requests = "==2.18.4" 9 | pipfile = "==0.0.2" 10 | oss2 = "==2.6.1" 11 | Flask = "==1.0.2" 12 | Flask-SQLAlchemy = "==2.3.2" 13 | Flask-WTF = "==0.14.2" 14 | Flask-Cors = "==2.1.0" 15 | Lin-CMS = "==0.2.0b2" 16 | certifi = "==2019.3.9" 17 | chardet = "==3.0.4" 18 | click = "==7.0" 19 | idna = "==2.6" 20 | itsdangerous = "==1.1.0" 21 | six = "==1.12.0" 22 | toml = "==0.10.0" 23 | urllib3 = "==1.22" 24 | pypinyin = "==0.37.0" 25 | pymongo = "==3.10.1" 26 | celery = "==4.4.2" 27 | xlrd = "==1.2.0" 28 | xlutils = "==2.0.0" 29 | flower = "==0.9.5" 30 | Flask-JWT-Extended = "==3.12.1" 31 | PyJWT = "==1.7.1" 32 | WTForms = "==2.2.1" 33 | Flask-APScheduler = "==1.11.0" 34 | Flask-Mail = "==0.9.1" 35 | Jinja2 = "==2.10" 36 | MarkupSafe = "==1.1.1" 37 | SQLAlchemy = "==1.2.11" 38 | Werkzeug = "==0.14.1" 39 | Flask-PyMongo = "==2.3.0" 40 | Flask-Celery-Helper = "==1.1.0" 41 | flask-socketio = "==3.0.2" 42 | python-engineio = "==3.13.2" 43 | python-socketio = "==4.2.0" 44 | 45 | [dev-packages] 46 | pytest = "*" 47 | 48 | [requires] 49 | python_version = "3.6" 50 | -------------------------------------------------------------------------------- /easy-test-flask/app/api/mock/mock.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from flask import request 4 | from lin.redprint import Redprint 5 | 6 | from app.libs.enums import CaseMethodEnum 7 | from app.libs.error_code import MethodMockException 8 | from app.libs.init import mongo 9 | 10 | mock_api = Redprint('mock') 11 | 12 | 13 | @mock_api.route('/', methods=['GET', 'POST', 'PUT', 'DELETE']) 14 | def mock(path): 15 | methodData = { 16 | 'GET': '1', 17 | 'POST': '2', 18 | 'PUT': '3', 19 | 'DELETE': '4', 20 | } 21 | mocks = mongo.db.mock.find( 22 | { 23 | 'url': '/mock/' + path, 24 | 'method': methodData[request.method], 25 | 'delete_time': None 26 | }, 27 | {"_id": 0}).sort([('_id', -1)]) 28 | 29 | mocks = list(mocks) 30 | # mock数据不存在 返回405 31 | if not mocks: 32 | raise MethodMockException() 33 | 34 | response_body = json.loads(mocks[0]['response_body']) if mocks[0]['response_body'] else '' 35 | status_code = mocks[0]['status_code'] 36 | response_header = json.loads(mocks[0]['response_header']) if mocks[0]['response_header'] else '' 37 | 38 | return response_body, status_code, response_header 39 | -------------------------------------------------------------------------------- /easy-test-vue/src/lin/mixin/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { mapGetters } from 'vuex' 3 | 4 | const globalMixin = { 5 | // eslint-disable-next-line 6 | install(Vue) { 7 | Vue.mixin({ 8 | methods: { 9 | goBack() { 10 | /* eslint-disable no-unused-expressions */ 11 | window.history.length > 1 ? this.$router.go(-1) : this.$router.push('/') 12 | }, 13 | isAllowed(_auth) { 14 | /* eslint-disable no-restricted-syntax */ 15 | /* eslint-disable guard-for-in */ 16 | const { auths } = this.user 17 | for (const mod of auths) { 18 | for (const item in mod) { 19 | for (const a of mod[item]) { 20 | // console.log(a.auth) 21 | if (a.auth === _auth) { 22 | return true 23 | } 24 | // console.log(a.module) 25 | } 26 | } 27 | } 28 | return false 29 | }, 30 | }, 31 | filters: { 32 | // ...filter, 33 | }, 34 | computed: { 35 | ...mapGetters(['user']), 36 | }, 37 | }) 38 | }, 39 | } 40 | 41 | Vue.use(globalMixin) 42 | 43 | export default globalMixin 44 | -------------------------------------------------------------------------------- /easy-test-vue/script/template/plugin/views/Stage1.vue.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 插件<%= camelCaseName %>舞台页面 4 | 5 | 6 | {{text}} 7 | 8 | 9 | 10 | 11 | 48 | 49 | 51 | -------------------------------------------------------------------------------- /easy-test-flask/app/libs/opreation_excel.py: -------------------------------------------------------------------------------- 1 | import xlrd 2 | from xlutils.copy import copy 3 | 4 | 5 | class OperationExcel: 6 | def __init__(self, file_path): 7 | self.file_path = file_path 8 | self.workbook = xlrd.open_workbook(file_path) 9 | self.workbook_copy = copy(self.workbook) 10 | self.table = None 11 | self.sheet_write = None 12 | self.rowNum = 0 13 | self.colNum = 0 14 | 15 | def get_table(self, sheet_name=None, sheet_id=0): 16 | self.table = self.workbook.sheets()[sheet_id] 17 | if sheet_name: 18 | self.table = self.workbook.sheet_by_name(sheet_name) 19 | 20 | def get_rowNum(self): 21 | self.rowNum = self.table.nrows 22 | 23 | def get_colNum(self): 24 | self.colNum = self.table.ncols 25 | 26 | def get_cell_value(self, x, y): 27 | cell_value = self.table.cell_value(x, y) 28 | return cell_value 29 | 30 | def get_sheet_write(self, sheetid=0): 31 | self.sheet_write = self.workbook_copy.get_sheet(sheetid) 32 | 33 | def write_execel(self, row, col, value): 34 | self.sheet_write.write(row, col, value) 35 | 36 | def write_save(self): 37 | self.workbook_copy.save(self.file_path) 38 | -------------------------------------------------------------------------------- /easy-test-vue/src/config/stage/test.js: -------------------------------------------------------------------------------- 1 | const testRouter = { 2 | route: null, 3 | name: null, 4 | title: '测试结果', 5 | type: 'folder', 6 | icon: 'iconfont icon-result', 7 | filePath: 'views/test/', 8 | order: 5, 9 | inNav: true, 10 | children: [ 11 | { 12 | title: '运行记录', 13 | type: 'view', 14 | name: 'testRecord', 15 | route: '/test/record', 16 | filePath: 'views/test/record/RecordList', 17 | inNav: true, 18 | icon: 'iconfont icon-executeRecord', 19 | keepAlive: true, 20 | permission: ['运行记录'], 21 | }, 22 | { 23 | title: '运行详情', 24 | type: 'view', 25 | name: 'testDetail', 26 | route: '/test/detail', 27 | filePath: 'views/test/detail/TestDetail.vue', 28 | inNav: true, 29 | icon: 'iconfont icon-executeDetail', 30 | keepAlive: true, 31 | permission: ['运行详情'], 32 | }, 33 | { 34 | title: '用例日志', 35 | type: 'view', 36 | name: 'caseLogs', 37 | route: '/test/log', 38 | filePath: 'views/test/log/LogList.vue', 39 | inNav: true, 40 | icon: 'iconfont icon-caseLog', 41 | keepAlive: true, 42 | permission: ['用例日志列表'], 43 | }, 44 | ], 45 | } 46 | 47 | export default testRouter 48 | -------------------------------------------------------------------------------- /easy-test-vue/src/lin/directives/authorize.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import store from '@/store' 3 | 4 | function isAllowed(_auth, user, auths) { 5 | if (user.isSuper) { 6 | return true 7 | } 8 | if (typeof _auth === 'string') { 9 | return auths.includes(_auth) 10 | } 11 | if (_auth instanceof Array) { 12 | return _auth.some(auth => auths.indexOf(auth) >= 0) 13 | } 14 | return false 15 | } 16 | 17 | Vue.directive('auth', { 18 | bind(el, binding) { 19 | let auth 20 | let type 21 | if (Object.prototype.toString.call(binding.value) === '[object Object]') { 22 | // eslint-disable-next-line prefer-destructuring 23 | auth = binding.value.auth 24 | // eslint-disable-next-line prefer-destructuring 25 | type = binding.value.type 26 | } else { 27 | auth = binding.value 28 | } 29 | const isAllow = isAllowed(auth, store.state.user || {}, store.state.auths) 30 | const element = el 31 | if (!isAllow && auth) { 32 | if (type) { 33 | element.disabled = true 34 | element.style.opacity = 0.4 35 | element.style.cursor = 'not-allowed' 36 | } else { 37 | element.style.display = 'none' 38 | } 39 | } 40 | }, 41 | }) 42 | 43 | export default Vue.directive('auth') 44 | -------------------------------------------------------------------------------- /easy-test-flask/app/libs/customize_deal.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import time 4 | 5 | 6 | def make_deal_file(data, fun, var_dick): 7 | template = """ 8 | # coding:utf-8 9 | interface_return = {{data}} 10 | var_dick = {{var_all}} 11 | 12 | 13 | {{fun}} 14 | 15 | 16 | print({{fun_name}}(interface_return, var_dick)) 17 | """ 18 | 19 | fun_name = re.findall(r'def (.*)\(', fun)[0] 20 | r = template.replace('{{data}}', data).replace('{{var_all}}', var_dick).replace('{{fun}}', fun).\ 21 | replace('{{fun_name}}', fun_name) 22 | 23 | # 将文件的true替换为True,false替换为False 24 | replace_true = re.sub('true', 'True', r) 25 | replace_false = re.sub('false', 'False', replace_true) 26 | content = replace_false 27 | 28 | tmp_directory = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + '/document/tmp/' 29 | if not os.path.exists(tmp_directory): 30 | os.makedirs(tmp_directory) 31 | file_name = fun_name + '_' + str(int(time.time() * 1000000)) + '.py' 32 | path = tmp_directory + file_name 33 | 34 | f = open(path, 'w', encoding='utf-8') 35 | f.write(content) 36 | f.close() 37 | 38 | return path 39 | 40 | 41 | def remove_deal_file(path): 42 | if os.path.exists(path): 43 | os.remove(path) 44 | 45 | -------------------------------------------------------------------------------- /easy-test-vue/public/tinymce/skins/content/document/content.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | @media screen { 8 | html { 9 | background: #f4f4f4; 10 | } 11 | } 12 | body { 13 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 14 | } 15 | @media screen { 16 | body { 17 | background-color: #fff; 18 | box-shadow: 0 0 4px rgba(0, 0, 0, 0.15); 19 | box-sizing: border-box; 20 | margin: 1rem auto 0; 21 | max-width: 820px; 22 | min-height: calc(99vh); 23 | padding: 4rem 6rem 6rem 6rem; 24 | } 25 | } 26 | table { 27 | border-collapse: collapse; 28 | } 29 | table th, 30 | table td { 31 | border: 1px solid #ccc; 32 | padding: .4rem; 33 | } 34 | blockquote { 35 | border-left: 2px solid #ccc; 36 | margin-left: 1.5rem; 37 | padding-left: 1rem; 38 | } 39 | figure figcaption { 40 | color: #999; 41 | margin-top: .25rem; 42 | text-align: center; 43 | } 44 | hr { 45 | border-color: #ccc; 46 | border-style: solid; 47 | border-width: 1px 0 0 0; 48 | } -------------------------------------------------------------------------------- /easy-test-vue/src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 30 | 31 | 60 | -------------------------------------------------------------------------------- /easy-test-vue/src/assets/styles/realize/mixin.scss: -------------------------------------------------------------------------------- 1 | 2 | // 根据不同的屏幕加载背景图片 3 | @mixin bi($url, $type: 'png') { 4 | background-image: url($url + "@2x." + $type); 5 | @media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2) { 6 | background-image: url($url + "@3x." + $type); 7 | background-size: cover; 8 | } 9 | } 10 | 11 | //文字超出后以...显示 支持多行 12 | @mixin fn-ellpisis($line: 1) { 13 | display: -webkit-box; 14 | -webkit-box-orient: vertical; 15 | -webkit-line-clamp: $line; 16 | overflow: hidden; 17 | } 18 | 19 | @mixin btn_scale_hover(){ 20 | transition: .4s all; 21 | &:hover{ 22 | transform: scale(1.1); 23 | } 24 | } 25 | 26 | @mixin bi-saturate_hover($background-color, $value: 20){ // 背景颜色增加饱和度 27 | transition: .6s all; 28 | &:hover{ 29 | background-color: saturate($background-color, $value); 30 | } 31 | } 32 | 33 | @mixin color-saturate_hover($color, $value: 30){ // 颜色增加饱和度 34 | transition: .6s all; 35 | &:hover{ 36 | color: saturate($color, $value); 37 | } 38 | } 39 | 40 | @mixin bi-lighten_hover($background-color, $value: 10){ // 背景颜色变浅。 41 | transition: .6s all; 42 | &:hover{ 43 | background-color: lighten($background-color, $value); 44 | } 45 | } 46 | 47 | @mixin bi-opacity_hover($opacity: .8){ // 透明度 48 | transition: .4s all; 49 | &:hover{ 50 | opacity: $opacity; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /easy-test-flask/starter.py: -------------------------------------------------------------------------------- 1 | """ 2 | :copyright: © 2019 by the Lin team. 3 | :license: MIT, see LICENSE for more details. 4 | """ 5 | import sys 6 | from app.app import create_app 7 | 8 | from app.libs.init import celery 9 | 10 | app = None 11 | environment = None 12 | env_param = [i for i in sys.argv if i.startswith('--env') or i.startswith('--host')] 13 | if env_param: 14 | environment = env_param[0].split('=')[1] 15 | if not environment or environment == 'dev': 16 | # 开发环境 17 | app = create_app(environment='development') 18 | elif environment == 'prod': 19 | # 生产环境 20 | app = create_app() 21 | 22 | celery.conf.update(imports='app.libs.tasks') 23 | 24 | 25 | @app.route('/', methods=['GET'], strict_slashes=False) 26 | def lin_slogan(): 27 | return """ 31 | 遇事不决 可问春风 """ 32 | 33 | 34 | if __name__ == '__main__': 35 | app.run(host='0.0.0.0') 36 | -------------------------------------------------------------------------------- /easy-test-vue/src/assets/styles/realize/animation.scss: -------------------------------------------------------------------------------- 1 | @keyframes fadeIn { 2 | from { 3 | opacity: 0.1; 4 | } 5 | to { 6 | opacity: 1; 7 | } 8 | } 9 | 10 | @keyframes img-add-view { 11 | 0% { 12 | opacity: 0; 13 | transform: scale(0.8); 14 | } 15 | 100% { 16 | opacity: 1; 17 | transform: scale(1); 18 | } 19 | } 20 | 21 | // 添加单品 22 | @keyframes arr-item-add { 23 | 0% { 24 | opacity: 0; 25 | transform: translateY(-100%); 26 | } 27 | 100% { 28 | opacity: 1; 29 | transform: translateY(0%); 30 | } 31 | } 32 | 33 | // 删除单品 34 | @keyframes arr-item-reduce { 35 | 0% { 36 | opacity: 1; 37 | transform: translateY(0%); 38 | } 39 | 100% { 40 | opacity: 0; 41 | transform: translateY(100%); 42 | } 43 | } 44 | 45 | // 页面切换动画 46 | .fade-transform-leave-active, 47 | .fade-transform-enter-active { 48 | transition: all 0.3s; 49 | } 50 | .fade-transform-enter { 51 | opacity: 0; 52 | transform: translateX(-30px); 53 | } 54 | .fade-transform-leave-to { 55 | opacity: 0; 56 | transform: translateX(30px); 57 | } 58 | 59 | .fadeChild-transform-leave-active, 60 | .fadeChild-transform-enter-active { 61 | transition: all 0.3s; 62 | } 63 | .fadeChild-transform-enter { 64 | opacity: 0; 65 | transform: translateX(-30px); 66 | } 67 | .fadeChild-transform-leave-to { 68 | opacity: 0; 69 | transform: translateX(30px); 70 | } 71 | -------------------------------------------------------------------------------- /easy-test-vue/src/lin/utils/search.js: -------------------------------------------------------------------------------- 1 | import FastScanner from 'fastscan' 2 | 3 | // const words = ['今日头条', 4 | // '微信', '支付宝', 5 | // ] 6 | // const scanner = new FastScanner(words) 7 | // const content = '今日头条小程序终于来了,这是继微信、支付宝、百度后,第四个推出小程序功能的App。猫眼电影率先试水,出现在今日头条。' 8 | // const offWords = scanner.search(content) 9 | // console.log(offWords) 10 | // const hits = scanner.hits(content) 11 | // console.log(hits) 12 | 13 | /** 14 | * 15 | * @param {string} word 16 | * @param {string} content 17 | */ 18 | export async function searchForWord(word, content) { 19 | const scanner = new FastScanner([word]) 20 | const offWords = scanner.search(content) 21 | return offWords 22 | } 23 | 24 | /** 25 | * 26 | * @param {Array} words 27 | * @param {string} content 28 | */ 29 | export async function searchForWords(words, content) { 30 | const scanner = new FastScanner(words) 31 | const offWords = scanner.search(content) 32 | return offWords 33 | } 34 | /** 35 | * 36 | * @param {string} keyword 37 | * @param {Array} logs 38 | */ 39 | export async function searchLogKeyword(keyword, logs, className = 'strong') { 40 | const _logs = logs.map(log => { 41 | let msg = log.message 42 | msg = msg.replace(RegExp(`${keyword}`, 'g'), `${keyword}`) 43 | // eslint-disable-next-line 44 | log.message = msg 45 | return log 46 | }) 47 | return _logs 48 | } 49 | -------------------------------------------------------------------------------- /easy-test-vue/src/components/layout/Breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | {{ item }} 7 | 8 | 9 | 10 | 11 | 30 | 31 | 66 | -------------------------------------------------------------------------------- /easy-test-vue/src/components/layout/NavBar.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 34 | 35 | 61 | -------------------------------------------------------------------------------- /easy-test-vue/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | }, 6 | plugins: ['vue'], 7 | extends: ['plugin:vue/essential', '@vue/airbnb'], 8 | rules: { 9 | 'linebreak-style': [0, 'error', 'windows'], 10 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 11 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'import/extensions': 0, // import不需要写文件扩展名 13 | 'import/no-unresolved': 0, 14 | // 'import/no-duplicates': 0, 15 | 'no-underscore-dangle': 0, // 无下划线 16 | camelcase: 0, // 变量可以用下划线 17 | semi: ['error', 'never'], // 无分号 18 | 'no-extra-semi': 0, // 和prettier冲突 19 | 'no-plusplus': 0, // 禁止使用++,-- 20 | // 'no-tabs': [o], 21 | 'guard-for-in': 0, 22 | 'max-len': ['error', { code: 200 }], 23 | 'no-restricted-syntax': 0, 24 | 'import/no-extraneous-dependencies': ['error', { devDependencies: ['script/**/*.js'] }], 25 | 'no-restricted-syntax': 0, 26 | 'class-methods-use-this': 'off', 27 | 'consistent-return': 'off', 28 | 'arrow-parens': ['error', 'as-needed'], 29 | 'object-curly-newline': [ 30 | 'error', 31 | { 32 | ImportDeclaration: 'never', 33 | }, 34 | ], 35 | 'comma-dangle': ['error', 'only-multiline'], 36 | 'no-param-reassign': ['error', { props: false }], 37 | }, 38 | parserOptions: { 39 | parser: 'babel-eslint', 40 | }, 41 | } 42 | -------------------------------------------------------------------------------- /easy-test-vue/src/store/mutations.js: -------------------------------------------------------------------------------- 1 | import * as types from './mutation-types' 2 | 3 | export default { 4 | [types.SET_LOGINED](state) { 5 | /* eslint no-param-reassign: 0 */ 6 | state.logined = true 7 | }, 8 | 9 | [types.REMOVE_LOGINED](state) { 10 | state.logined = false 11 | state.user = null 12 | }, 13 | 14 | [types.SET_USER](state, payload) { 15 | state.user = payload 16 | }, 17 | 18 | [types.ADD_READED_MESSAGE](state, payload) { 19 | state.readedMessages.push(payload) 20 | }, 21 | 22 | [types.ADD_UNREAD_MESSAGE](state, payload) { 23 | // console.log('===: ', payload) 24 | state.unreadMessages.push(payload) 25 | }, 26 | 27 | [types.REMOVE_UNREAD_MESSAGE](state, payload) { 28 | // payload => message.id 29 | const { unreadMessages } = state 30 | const index = unreadMessages.findIndex(el => el.id === payload) 31 | unreadMessages.splice(index, 1) 32 | }, 33 | 34 | [types.SET_USER_AUTHS](state, auths) { 35 | const _auths = [] 36 | for (let i = 0; i < auths.length; i++) { 37 | for (const key in auths[i]) { 38 | // console.log(i, state.user.auths[i][key]) 39 | for (let j = 0; j < auths[i][key].length; j++) { 40 | _auths.push(auths[i][key][j].auth) 41 | } 42 | } 43 | } 44 | state.auths = _auths 45 | }, 46 | 47 | [types.SET_REFERSH_OPTION](state, option) { 48 | state.refreshOptions = option 49 | }, 50 | } 51 | -------------------------------------------------------------------------------- /easy-test-vue/script/lib/plugin-get-all.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | const path = require('path') 3 | const chalk = require('chalk') 4 | 5 | // 验证是否是插件 6 | function isPlugin(source) { 7 | let result = true 8 | if (!fs.lstatSync(source).isDirectory()) { 9 | return false 10 | } 11 | const configPath = path.resolve(source, './stage-config.js') 12 | const packagePath = path.resolve(source, './package.json') 13 | if (result && !fs.existsSync(configPath)) { 14 | result = false 15 | } 16 | if (result && !fs.existsSync(packagePath)) { 17 | result = false 18 | } 19 | if (!result) { 20 | console.log(chalk.yellow(`${source} 不符合 Lin-CMS 插件规范`)) 21 | } 22 | 23 | return result 24 | } 25 | 26 | function getPlugins(source) { 27 | if (!fs.existsSync(source)) { 28 | console.log(chalk.yellow(`目录不存在: ${source}`)) 29 | return [] 30 | } 31 | const folders = fs.readdirSync(source) 32 | const pluginsList = [] 33 | 34 | folders.forEach((item) => { 35 | const itemPath = path.join(source, item) 36 | if (!isPlugin(itemPath)) { 37 | return 38 | } 39 | const config = {} 40 | config.name = item 41 | config.path = path.resolve(__dirname, `../src/plugins/${item}/`) 42 | config.packageCtx = JSON.parse(fs.readFileSync(path.resolve(itemPath, './package.json'), 'utf8')) 43 | pluginsList.push(config) 44 | }) 45 | 46 | return pluginsList 47 | } 48 | 49 | module.exports = getPlugins 50 | -------------------------------------------------------------------------------- /easy-test-vue/src/lin/plugins/preview/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Preview from '@/components/base/preview/preview' 3 | 4 | const previewImage = {} 5 | previewImage.install = vue => { 6 | // eslint-disable-line 7 | const PreviewConstructor = vue.extend(Preview) 8 | 9 | let instance = null 10 | 11 | // eslint-disable-next-line func-names 12 | PreviewConstructor.prototype.close = function () { 13 | this.data = [] 14 | this.options = {} 15 | this.imageIndex = 0 16 | } 17 | 18 | const getInstance = () => { 19 | if (!instance) { 20 | instance = new PreviewConstructor() 21 | } 22 | return instance 23 | } 24 | 25 | vue.prototype.$imagePreview = (opts = {}) => { 26 | // eslint-disable-line 27 | const elem = document.createElement('div') 28 | if (!instance) { 29 | let myInstance = getInstance() 30 | vue.prototype.$previewInstance = myInstance // eslint-disable-line 31 | myInstance.$mount(elem) 32 | myInstance.data = opts.images || [] 33 | myInstance.imageIndex = opts.index || 0 34 | myInstance.options = opts.defaultOpt || {} 35 | document.body.appendChild(myInstance.$el) 36 | myInstance.$on('close', () => { 37 | myInstance.close() 38 | document.body.removeChild(myInstance.$el) 39 | myInstance.$destroy() 40 | myInstance = null 41 | instance = null 42 | }) 43 | } 44 | } 45 | } 46 | 47 | Vue.use(previewImage) 48 | 49 | export default previewImage 50 | -------------------------------------------------------------------------------- /easy-test-vue/src/components/base/search/lin-search.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 40 | 64 | -------------------------------------------------------------------------------- /easy-test-flask/app/validators/SchedulerForm.py: -------------------------------------------------------------------------------- 1 | from lin.forms import Form 2 | from wtforms import StringField, FieldList, IntegerField, BooleanField, FormField, Field 3 | from wtforms.validators import DataRequired, Optional, length 4 | 5 | 6 | class SchedulerForm(Form): 7 | # 工程 id 8 | project = IntegerField(validators=[DataRequired(message='请输入工程id')]) 9 | user = IntegerField(validators=[DataRequired(message='请输入维护人员')]) 10 | sendEmail = BooleanField(validators=[Optional()]) 11 | copyPerson = StringField(length(max=50, message='抄送人需小于50字符'), validators=[Optional()]) 12 | cron = StringField(length(max=30, message='cron表达式需小于30字符'), validators=[DataRequired(message='请输入cron表达式')]) 13 | # 邮件发送策略 14 | emailStrategy = IntegerField(default=1) 15 | 16 | 17 | class SchedulerEditForm(Form): 18 | user = IntegerField(validators=[DataRequired(message='请输入维护人员')]) 19 | sendEmail = BooleanField(validators=[Optional()]) 20 | copyPerson = StringField(length(max=50, message='抄送人需小于50字符'), validators=[Optional()]) 21 | cron = StringField(length(max=30, message='cron表达式需小于30字符'), validators=[DataRequired(message='请输入cron表达式')]) 22 | # 邮件发送策略 23 | emailStrategy = IntegerField(default=1) 24 | 25 | 26 | class SchedulerSearchForm(Form): 27 | page = IntegerField(default=1) 28 | count = IntegerField(default=10) 29 | project = IntegerField() 30 | user = IntegerField() 31 | 32 | 33 | class SchedulerOperateForm(Form): 34 | schedulerId = StringField(validators=[DataRequired(message='不许为空')]) 35 | -------------------------------------------------------------------------------- /easy-test-vue/src/plugins/custom/stage-config.js: -------------------------------------------------------------------------------- 1 | const CustomRouter = { 2 | route: null, 3 | name: null, 4 | title: '自定义组件', 5 | type: 'folder', 6 | icon: 'iconfont icon-zidingyi', 7 | filePath: 'views/custom/', 8 | order: null, 9 | inNav: true, 10 | permission: ['查看lin的信息'], 11 | children: [ 12 | { 13 | title: 'upload 图像上传', 14 | type: 'view', 15 | name: 'ImgsUploadDemo', 16 | route: '/imgs-upload/stage1', 17 | filePath: 'plugins/custom/views/Demo.vue', 18 | inNav: true, 19 | icon: 'iconfont icon-zidingyi', 20 | permission: null, 21 | }, 22 | { 23 | title: 'gallery 画廊', 24 | type: 'view', 25 | name: 'GalleryDemo', 26 | route: '/custom/gallery', 27 | filePath: 'plugins/custom/views/Gallery.vue', 28 | inNav: true, 29 | icon: 'iconfont icon-zidingyi', 30 | permission: null, 31 | }, 32 | { 33 | title: '富文本', 34 | type: 'view', 35 | name: 'Tinymce', 36 | route: '/custom/tinymce', 37 | filePath: 'plugins/custom/views/Tinymce.vue', 38 | inNav: true, 39 | icon: 'iconfont icon-zidingyi', 40 | permission: null, 41 | }, 42 | { 43 | title: 'multiple 多重输入', 44 | type: 'view', 45 | name: 'Multiple', 46 | route: '/custom/multiple', 47 | filePath: 'plugins/custom/views/MultipleInput.vue', 48 | inNav: true, 49 | icon: 'iconfont icon-zidingyi', 50 | permission: null, 51 | }, 52 | ], 53 | } 54 | 55 | export default CustomRouter 56 | -------------------------------------------------------------------------------- /easy-test-vue/src/config/stage/project.js: -------------------------------------------------------------------------------- 1 | const projectRouter = { 2 | route: null, 3 | name: null, 4 | title: '工程管理', 5 | type: 'folder', 6 | icon: 'iconfont icon-manager', 7 | filePath: 'views/project/', 8 | order: 4, 9 | inNav: true, 10 | children: [ 11 | { 12 | title: '工程配置', 13 | type: 'view', 14 | name: 'projectConfig', 15 | route: '/project/config', 16 | filePath: 'views/project/ProjectConfig.vue', 17 | inNav: true, 18 | icon: 'iconfont icon-config', 19 | keepAlive: true, 20 | permission: ['工程配置'], 21 | }, 22 | { 23 | route: '/project/list', 24 | name: null, 25 | title: '工程列表', 26 | type: 'tab', // 取 route 为默认加载页 27 | icon: 'iconfont icon-list', 28 | filePath: 'views/case/group', 29 | inNav: true, 30 | permission: ['工程列表'], 31 | children: [ 32 | { 33 | route: '/project/list', 34 | type: 'view', 35 | name: 'projectList', 36 | inNav: true, 37 | filePath: 'views/project/ProjectList.vue', 38 | title: '工程列表', 39 | icon: 'iconfont icon-list', 40 | keepAlive: false, 41 | }, 42 | { 43 | route: '/project/add', 44 | type: 'view', 45 | name: 'projectAdd', 46 | filePath: 'views/project/ProjectAdd.vue', 47 | inNav: true, 48 | title: '添加工程', 49 | icon: 'iconfont icon-add', 50 | keepAlive: false, 51 | }, 52 | ], 53 | }, 54 | ], 55 | } 56 | 57 | export default projectRouter 58 | -------------------------------------------------------------------------------- /easy-test-flask/app/api/v1/CaseGroup.py: -------------------------------------------------------------------------------- 1 | """ 2 | @Time : 2020/3/14 10:13 3 | @Author : 郭家兴 4 | @Email : 302802003@qq.com 5 | @File : CaseGroup.py 6 | @Desc : 7 | """ 8 | from flask import jsonify 9 | from lin import login_required, route_meta, group_required 10 | from lin.exception import Success 11 | from lin.redprint import Redprint 12 | 13 | from app.models.CaseGroup import CaseGroup 14 | from app.validators.CaseForm import CaseGroupForm 15 | 16 | case_group_api = Redprint('caseGroup') 17 | 18 | 19 | @case_group_api.route('', methods=['POST']) 20 | @login_required 21 | def create_group(): 22 | form = CaseGroupForm().validate_for_api() 23 | CaseGroup.new_group(form) 24 | return Success(msg='新建分组成功') 25 | 26 | 27 | @case_group_api.route('', methods=['GET']) 28 | @route_meta('用例分组', module='用例') 29 | @group_required 30 | def get_groups(): 31 | groups = CaseGroup.get_all() 32 | return jsonify(groups) 33 | 34 | 35 | @case_group_api.route('/', methods=['PUT']) 36 | @login_required 37 | def update_group(gid): 38 | form = CaseGroupForm().validate_for_api() 39 | CaseGroup.edit_group(gid, form) 40 | return Success(msg='更新分组成功') 41 | 42 | 43 | @case_group_api.route('/', methods=['DELETE']) 44 | @route_meta('删除用例分组', module='用例') 45 | @group_required 46 | def delete_group(gid): 47 | CaseGroup.remove_group(gid) 48 | return Success(msg='删除分组成功') 49 | 50 | 51 | @case_group_api.route('/auth', methods=['GET']) 52 | @login_required 53 | def get_auth_groups(): 54 | """获取当前登陆用户的授权分组""" 55 | groups = CaseGroup.get_auth() 56 | return jsonify(groups) 57 | -------------------------------------------------------------------------------- /easy-test-flask/app/api/v1/user.py: -------------------------------------------------------------------------------- 1 | from flask import jsonify 2 | from lin import login_required 3 | from lin.redprint import Redprint 4 | 5 | from app.models.case import Case 6 | from app.models.project import Project 7 | from app.models.scheduler import Scheduler 8 | from app.models.task import Task 9 | from app.validators.MineForm import MineSearchForm 10 | 11 | user_api = Redprint('user') 12 | 13 | 14 | # 我创建的用例 15 | @user_api.route('/case', methods=['GET']) 16 | @login_required 17 | def case(): 18 | form = MineSearchForm().validate_for_api() 19 | cases = Case.user_case(form.uid.data, form.name.data, form.page.data, form.count.data) 20 | return jsonify(cases) 21 | 22 | 23 | # 我维护的工程 24 | @user_api.route('/project', methods=['GET']) 25 | @login_required 26 | def project(): 27 | form = MineSearchForm().validate_for_api() 28 | projects = Project.user_project(form.uid.data, form.name.data, form.page.data, form.count.data) 29 | return jsonify(projects) 30 | 31 | 32 | # 我维护的定时任务 33 | @user_api.route('/scheduler', methods=['GET']) 34 | @login_required 35 | def scheduler(): 36 | form = MineSearchForm().validate_for_api() 37 | schedulers = Scheduler.user_scheduler(form.uid.data, form.name.data, form.page.data, form.count.data) 38 | return jsonify(schedulers) 39 | 40 | 41 | # 我执行的记录 42 | @user_api.route('/task', methods=['GET']) 43 | @login_required 44 | def task(): 45 | form = MineSearchForm().validate_for_api() 46 | tasks = Task.user_task(form.uid.data, form.name.data, form.start.data, form.end.data, form.page.data, 47 | form.count.data) 48 | return jsonify(tasks) 49 | -------------------------------------------------------------------------------- /easy-test-mini/pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 获取头像昵称 12 | 获取头像昵称 13 | 请使用1.4.4及以上版本基础库 14 | 15 | 16 | 17 | {{userInfo.nickName}} 18 | 19 | 20 | 21 | {{motto}} 22 | 23 | ©2020-2022 easy-test created by 郭家兴 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /easy-test-vue/src/lin/utils/sse.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | // import EventSourcePolyfill from 'event-source-polyfill' 3 | import 'event-source-polyfill/src/eventsource' 4 | import { getToken } from './cookie' 5 | import store from '../../store' 6 | 7 | export default class Sse { 8 | source = null 9 | 10 | /** 11 | * 需在vuex中确认有user对象后才能初始化,否则不连接服务器 12 | * 注意: sse单独走自己的请求路线,不与axios重合,所以axios里面的配置在此处失效 13 | * @param {string} url sse全路径 14 | * @param {Array} events 当前用户可监听的路径 15 | */ 16 | constructor(url, events) { 17 | /* eslint-disable no-undef */ 18 | console.log(url, events) 19 | this.source = new EventSourcePolyfill(url, { 20 | headers: { 21 | Authorization: getToken('access_token'), 22 | }, 23 | }) 24 | this.open() 25 | 26 | events.forEach(event => { 27 | this.addEventListener(event) 28 | }) 29 | } 30 | 31 | open() { 32 | this.source.onopen = event => { 33 | console.log('sse opened', event) 34 | } 35 | } 36 | 37 | error() { 38 | this.source.onerror = event => { 39 | console.log('error', event) 40 | } 41 | } 42 | 43 | addEventListener(eventName) { 44 | this.source.addEventListener(eventName, event => { 45 | // console.log('receive one message: ', event.data) 46 | // console.log('receive one message: ', event.lastEventId) 47 | store.commit('ADD_UNREAD_MESSAGE', { data: event.data, id: event.lastEventId }) 48 | Vue.prototype.$notify({ 49 | title: '您有新的消息', 50 | dangerouslyUseHTMLString: true, 51 | message: `${JSON.parse(event.data).message}`, 52 | }) 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /easy-test-vue/src/components/layout/Screenfull.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 49 | 50 | 75 | -------------------------------------------------------------------------------- /easy-test-vue/tests/unit/LIcon.test.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import LIcon from '@/components/base/icon/lin-icon.vue' 3 | 4 | describe('LIcon', () => { 5 | const wrapper 6 | let vm 7 | beforeEach(() => { 8 | wrapper = mount(LIcon) 9 | vm = wrapper.vm 10 | }) 11 | it('Icon.vue', () => { 12 | mount(LIcon) 13 | expect(LIcon).toBe.ok 14 | }) 15 | it('可以设置 name', () => { 16 | wrapper.setProps({ name: 'loading' }) 17 | const elements = vm.$el.querySelectorAll('use') 18 | expect(elements.length).toBe(1) 19 | expect(elements[0].getAttribute('xlink:href')).toBe('#icon-loading') 20 | }) 21 | it('可以设置 color', () => { 22 | wrapper.setProps({ color: '#ccc' }) 23 | const elements = vm.$el.querySelectorAll('use') 24 | expect(elements.length).toBe(1) 25 | expect(elements[0].getAttribute('fill')).toBe('#ccc') 26 | }) 27 | it('可以设置 width', () => { 28 | wrapper.setProps({ width: '30px' }) 29 | expect(wrapper.find('svg').element.style.width).toBe('30px') 30 | }) 31 | it('可以设置 height', () => { 32 | wrapper.setProps({ height: '30px' }) 33 | expect(wrapper.find('svg').element.style.height).toBe('30px') 34 | }) 35 | it('综合测试', () => { 36 | wrapper.setProps({ 37 | name: 'loading', color: '#ccc', width: '30px', height: '30px', 38 | }) 39 | const elements = vm.$el.querySelectorAll('use') 40 | expect(elements.length).toBe(1) 41 | expect(elements[0].getAttribute('xlink:href')).toBe('#icon-loading') 42 | expect(elements[0].getAttribute('fill')).toBe('#ccc') 43 | expect(wrapper.find('svg').element.style.height).toBe('30px') 44 | expect(wrapper.find('svg').element.style.width).toBe('30px') 45 | vm.$destroy() 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /easy-test-flask/app/api/v1/book.py: -------------------------------------------------------------------------------- 1 | """ 2 | a standard CRUD template of book 3 | 通过 图书 来实现一套标准的 CRUD 功能,供学习 4 | :copyright: © 2019 by the Lin team. 5 | :license: MIT, see LICENSE for more details. 6 | """ 7 | from flask import jsonify 8 | from lin import route_meta, group_required, login_required 9 | from lin.exception import Success 10 | from lin.redprint import Redprint 11 | 12 | from app.models.book import Book 13 | from app.validators.forms import BookSearchForm, CreateOrUpdateBookForm 14 | 15 | book_api = Redprint('book') 16 | 17 | 18 | # 这与真实的情况是一致的,因为一般的情况下,重要的接口需要被保护,重要的消息才需要推送 19 | @book_api.route('/', methods=['GET']) 20 | @login_required 21 | def get_book(bid): 22 | book = Book.get_detail(bid) 23 | return jsonify(book) 24 | 25 | 26 | @book_api.route('', methods=['GET']) 27 | @login_required 28 | def get_books(): 29 | books = Book.get_all() 30 | return jsonify(books) 31 | 32 | 33 | @book_api.route('/search', methods=['GET']) 34 | def search(): 35 | form = BookSearchForm().validate_for_api() 36 | books = Book.search_by_keywords(form.q.data) 37 | return jsonify(books) 38 | 39 | 40 | @book_api.route('', methods=['POST']) 41 | def create_book(): 42 | form = CreateOrUpdateBookForm().validate_for_api() 43 | Book.new_book(form) 44 | return Success(msg='新建图书成功') 45 | 46 | 47 | @book_api.route('/', methods=['PUT']) 48 | def update_book(bid): 49 | form = CreateOrUpdateBookForm().validate_for_api() 50 | Book.edit_book(bid, form) 51 | return Success(msg='更新图书成功') 52 | 53 | 54 | @book_api.route('/', methods=['DELETE']) 55 | @route_meta(auth='删除图书', module='图书') 56 | @group_required 57 | def delete_book(bid): 58 | Book.remove_book(bid) 59 | return Success(msg='删除图书成功') 60 | -------------------------------------------------------------------------------- /easy-test-vue/src/main.js: -------------------------------------------------------------------------------- 1 | import '@babel/polyfill' 2 | import Vue from 'vue' 3 | import ElementUI from 'element-ui' 4 | 5 | import VueCodemirror from 'vue-codemirror' 6 | 7 | 8 | import '@/lin/mixin' 9 | import '@/lin/filter' 10 | import '@/lin/plugins' 11 | import '@/lin/directives' 12 | 13 | import echarts from 'echarts' 14 | 15 | import xss from 'xss' 16 | 17 | import VueSocketIO from 'vue-socket.io' 18 | import CollapseTransition from 'element-ui/lib/transitions/collapse-transition' 19 | import router from '@/router' 20 | import store from '@/store' 21 | import App from '@/App.vue' 22 | 23 | import StickyTop from '@/components/base/sticky-top/sticky-top' 24 | import LIcon from '@/components/base/icon/lin-icon' 25 | import SourceCode from '@/components/base/source-code/source-code' 26 | 27 | import '@/assets/styles/index.scss' // eslint-disable-line 28 | import '@/assets/styles/realize/element-variables.scss' 29 | import 'element-ui/lib/theme-chalk/display.css' 30 | 31 | /* eslint-disable*/ 32 | import 'codemirror/lib/codemirror.css' 33 | 34 | Vue.prototype.$echarts = echarts 35 | Vue.prototype.$xss = xss 36 | 37 | Vue.config.productionTip = false 38 | 39 | Vue.use(ElementUI) 40 | 41 | Vue.use(VueCodemirror) 42 | 43 | Vue.component(CollapseTransition.name, CollapseTransition) 44 | 45 | // base 组件注册 46 | Vue.component('sticky-top', StickyTop) 47 | Vue.component('l-icon', LIcon) 48 | Vue.component('source-code', SourceCode) 49 | 50 | Vue.use(new VueSocketIO({ 51 | debug: true, 52 | connection: process.env.VUE_APP_SOCKETIO_URL, 53 | // connection: 'http://127.0.0.1:5000/', 54 | })) 55 | 56 | /* eslint no-unused-vars: 0 */ 57 | const AppInstance = new Vue({ 58 | router, 59 | store, 60 | render: h => h(App), 61 | }).$mount('#app') 62 | 63 | // 设置 App 实例 64 | window.App = AppInstance 65 | -------------------------------------------------------------------------------- /easy-test-flask/app/api/v1/overview.py: -------------------------------------------------------------------------------- 1 | from flask import jsonify 2 | from lin import route_meta, group_required, login_required 3 | from lin.redprint import Redprint 4 | 5 | from app.libs.error_code import ProjectNotFound 6 | from app.models.case import Case 7 | from app.models.mock import Mock 8 | from app.models.project import Project 9 | from app.models.scheduler import Scheduler 10 | from app.models.task import Task 11 | from app.models.user import User 12 | 13 | overview_api = Redprint('overview') 14 | 15 | 16 | @overview_api.route('/total', methods=['GET']) 17 | @login_required 18 | def total(): 19 | return { 20 | 'project': Project.total(), 21 | 'case': Case.total(), 22 | 'scheduler': Scheduler.total(), 23 | 'mock': Mock.total() 24 | } 25 | 26 | 27 | @overview_api.route('/today', methods=['GET']) 28 | @login_required 29 | def today(): 30 | test_count, test_project_count = Task.today() 31 | return { 32 | 'case_add_count': Case.today(), 33 | 'test_count': test_count, 34 | 'test_project_count': test_project_count 35 | } 36 | 37 | 38 | @overview_api.route('/projectTop', methods=['GET']) 39 | @login_required 40 | def project_top(): 41 | return Project.rate_top() 42 | 43 | 44 | @overview_api.route('/userTop', methods=['GET']) 45 | @login_required 46 | def user_top(): 47 | return User.execute_top() 48 | 49 | 50 | @overview_api.route('/caseTop', methods=['GET']) 51 | @login_required 52 | def case_top(): 53 | return Case.top() 54 | 55 | 56 | @overview_api.route('/project/', methods=['GET']) 57 | @login_required 58 | def project_collect(pid): 59 | project = Project.query.filter_by(id=pid).first() 60 | if not project: 61 | raise ProjectNotFound(msg='暂无工程数据') 62 | return project.collect() 63 | 64 | -------------------------------------------------------------------------------- /easy-test-vue/src/components/base/date-picker/lin-date-picker.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 18 | 19 | 20 | 66 | -------------------------------------------------------------------------------- /easy-test-flask/app/extensions/file/local_uploader.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from flask import current_app 4 | from werkzeug.utils import secure_filename 5 | 6 | from lin.core import File 7 | from lin.file import Uploader 8 | 9 | 10 | class LocalUploader(Uploader): 11 | 12 | def upload(self): 13 | ret = [] 14 | self.mkdir_if_not_exists() 15 | site_domain = current_app.config.get('SITE_DOMAIN')\ 16 | if current_app.config.get('SITE_DOMAIN') else 'http://127.0.0.1:5000' 17 | for single in self._file_storage: 18 | file_md5 = self._generate_md5(single.read()) 19 | single.seek(0) 20 | exists = File.query.filter_by(md5=file_md5).first() 21 | if exists: 22 | ret.append({ 23 | "key": single.name, 24 | "id": exists.id, 25 | "path": exists.path, 26 | "url": site_domain + os.path.join(current_app.static_url_path, exists.path) 27 | }) 28 | else: 29 | absolute_path, relative_path, real_name = self._get_store_path(single.filename) 30 | secure_filename(single.filename) 31 | single.save(absolute_path) 32 | file = File.create_file( 33 | name=real_name, 34 | path=relative_path, 35 | extension=self._get_ext(single.filename), 36 | size=self._get_size(single), 37 | md5=file_md5, 38 | commit=True 39 | ) 40 | ret.append({ 41 | "key": single.name, 42 | "id": file.id, 43 | "path": file.path, 44 | "url": site_domain + os.path.join(current_app.static_url_path, file.path) 45 | }) 46 | return ret 47 | -------------------------------------------------------------------------------- /easy-test-flask/app/plugins/poem/app/model.py: -------------------------------------------------------------------------------- 1 | from lin import db 2 | from lin.core import lin_config 3 | from lin.exception import NotFound 4 | from lin.interface import InfoCrud as Base 5 | from sqlalchemy import Column, String, Integer, Text, text 6 | 7 | 8 | class Poem(Base): 9 | __tablename__ = 'lin_poem' 10 | id = Column(Integer, primary_key=True, autoincrement=True) 11 | title = Column(String(50), nullable=False, comment='标题') 12 | author = Column(String(50), default='未名', comment='作者') 13 | dynasty = Column(String(50), default='未知', comment='朝代') 14 | _content = Column('content', Text, nullable=False, comment='内容,以/来分割每一句,以|来分割宋词的上下片') 15 | image = Column(String(255), default='', comment='配图') 16 | 17 | @property 18 | def content(self): 19 | ret = [] 20 | lis = self._content.split('|') 21 | for x in lis: 22 | ret.append(x.split('/')) 23 | return ret 24 | 25 | def get_all(self, form): 26 | query = self.query.filter_by(delete_time=None) 27 | 28 | if form.author.data: 29 | query = query.filter_by(author=form.author.data) 30 | 31 | limit = form.count.data if\ 32 | form.count.data else lin_config.get_config('poem.limit') 33 | 34 | poems = query.limit(limit).all() 35 | 36 | if not poems: 37 | raise NotFound(msg='没有找到相关诗词') 38 | return poems 39 | 40 | def search(self, q): 41 | poems = self.query.filter(Poem.title.like('%' + q + '%')).all() 42 | if not poems: 43 | raise NotFound(msg='没有找到相关诗词') 44 | return poems 45 | 46 | @classmethod 47 | def get_authors(cls): 48 | authors = db.session.query(cls.author).filter_by(soft=False).group_by( 49 | text('author')).having(text('count(author) > 0')).all() 50 | ret = [author[0] for author in authors] 51 | return ret 52 | -------------------------------------------------------------------------------- /easy-test-flask/tests/test_book.py: -------------------------------------------------------------------------------- 1 | """ 2 | :copyright: © 2019 by the Lin team. 3 | :license: MIT, see LICENSE for more details. 4 | """ 5 | from app.app import create_app 6 | from tests.utils import get_token 7 | 8 | app = create_app(environment='development') 9 | 10 | 11 | def test_create(): 12 | with app.test_client() as c: 13 | rv = c.post('/v1/book/', json={ 14 | 'title': '论如何做单测', 15 | 'author': 'pedro', 16 | 'summary': '在写这章之前,笔者一直很踌躇,因为我并没有多年的开发经验,甚至是一年都没有。换言之,我还没有一个良好的软件开发习惯,没有一个标准的开发约束,如果你和我一样,那么请你一定要仔细阅读本小节,并且开始尝试认真,仔细的做单测,它将会让你受益匪浅。', 17 | 'image': 'https://img3.doubanio.com/lpic/s1470003.jpg' 18 | }) 19 | json_data = rv.get_json() 20 | print(json_data) 21 | assert json_data['msg'] == '新建图书成功' 22 | assert rv.status_code == 201 23 | 24 | 25 | def test_update(): 26 | with app.test_client() as c: 27 | rv = c.put('/v1/book/7', json={ 28 | 'title': '论如何做单测', 29 | 'author': 'pedro & erik', 30 | 'summary': '在写这章之前,笔者一直很踌躇,因为我并没有多年的开发经验,甚至是一年都没有。换言之,我还没有一个良好的软件开发习惯,没有一个标准的开发约束,如果你和我一样', 31 | 'image': 'https://img3.doubanio.com/lpic/s1470003.jpg' 32 | }) 33 | json_data = rv.get_json() 34 | print(json_data) 35 | assert json_data['msg'] == '更新图书成功' 36 | assert rv.status_code == 201 37 | 38 | 39 | def test_delete(): 40 | with app.test_client() as c: 41 | rv = c.delete('/v1/book/7', headers={ 42 | 'Authorization': 'Bearer ' + get_token() 43 | }) 44 | json_data = rv.get_json() 45 | print(json_data) 46 | assert json_data['msg'] == '删除图书成功' 47 | assert rv.status_code == 201 48 | 49 | 50 | def test_get_books(): 51 | with app.test_client() as c: 52 | rv = c.get('/v1/book/') 53 | json_data = rv.get_json() 54 | print(json_data) 55 | assert rv.status_code == 200 56 | -------------------------------------------------------------------------------- /easy-test-flask/app/libs/case_uploader.py: -------------------------------------------------------------------------------- 1 | from lin.file import Uploader 2 | import os 3 | 4 | from flask import current_app 5 | from werkzeug.datastructures import FileStorage 6 | from werkzeug.utils import secure_filename 7 | 8 | from lin.core import File 9 | 10 | 11 | class CaseUploader(Uploader): 12 | 13 | def __init__(self, files: list or FileStorage, config): 14 | super().__init__(files, config) 15 | self.config = config 16 | 17 | def upload(self): 18 | ret = [] 19 | self.mkdir_if_not_exists() 20 | for single in self._file_storage: 21 | file_md5 = self._generate_md5(single.read()) 22 | single.seek(0) 23 | exists = File.query.filter_by(md5=file_md5).first() 24 | if exists: 25 | ret.append({ 26 | "key": single.name, 27 | "id": exists.id, 28 | "path": exists.path, 29 | "url": os.path.join(current_app.root_path, 'document/excel/upload', exists.path).replace('\\', '/') 30 | }) 31 | else: 32 | absolute_path, relative_path, real_name = self._get_store_path(single.filename) 33 | secure_filename(single.filename) 34 | single.save(absolute_path) 35 | file = File.create_file( 36 | name=real_name, 37 | path=relative_path, 38 | extension=self._get_ext(single.filename), 39 | size=self._get_size(single), 40 | md5=file_md5, 41 | commit=True 42 | ) 43 | ret.append({ 44 | "key": single.name, 45 | "id": file.id, 46 | "path": file.path, 47 | "url": os.path.join(current_app.root_path, 'document/excel/upload', file.path).replace('\\', '/') 48 | }) 49 | return ret 50 | -------------------------------------------------------------------------------- /easy-test-flask/app/libs/error_code.py: -------------------------------------------------------------------------------- 1 | """ 2 | :copyright: © 2019 by the Lin team. 3 | :license: MIT, see LICENSE for more details. 4 | """ 5 | 6 | from lin.exception import APIException 7 | 8 | 9 | class BookNotFound(APIException): 10 | code = 404 # http状态码 11 | msg = '没有找到相关图书' # 异常信息 12 | error_code = 80010 # 约定的异常码 13 | 14 | 15 | class RefreshException(APIException): 16 | code = 401 17 | msg = 'refresh token 获取失败' 18 | error_code = 10100 19 | 20 | 21 | class CaseGroupDeleteException(APIException): 22 | code = 401 23 | msg = '删除测试用例分组失败' 24 | error_code = 91001 25 | 26 | 27 | class ProjectConfigException(APIException): 28 | code = 403 29 | msg = '保存配置异常' 30 | error_code = 92001 31 | 32 | 33 | class ConfigNotFound(APIException): 34 | code = 404 35 | msg = '配置不存在' 36 | error_code = 92002 37 | 38 | 39 | class ProjectNotFound(APIException): 40 | code = 404 41 | msg = '工程不存在' 42 | error_code = 92003 43 | 44 | 45 | class CaseRemoveException(APIException): 46 | code = 403 47 | msg = '用例删除失败' 48 | error_code = 93001 49 | 50 | 51 | class CaseUploadExcelException(APIException): 52 | code = 400 53 | msg = 'excel用例数据异常' 54 | error_code = 93002 55 | 56 | 57 | class CaseDownloadException(APIException): 58 | code = 400 59 | msg = '用例导出数据异常' 60 | error_code = 93003 61 | 62 | 63 | class RecordRemoveException(APIException): 64 | code = 403 65 | msg = '运行记录删除失败' 66 | error_code = 94001 67 | 68 | 69 | class AddMockException(APIException): 70 | code = 400 71 | msg = '新增mock数据失败' 72 | error_code = 95001 73 | 74 | 75 | class EditMockException(APIException): 76 | code = 400 77 | msg = '编辑mock数据失败' 78 | error_code = 95002 79 | 80 | 81 | class MethodMockException(APIException): 82 | code = 405 83 | msg = '请求方法错误' 84 | error_code = 95003 85 | 86 | 87 | class RequestParamException(APIException): 88 | code = 400 89 | msg = '请求参数异常' 90 | error_code = 96001 91 | -------------------------------------------------------------------------------- /easy-test-flask/app/plugins/poem/README.md: -------------------------------------------------------------------------------- 1 | # 古诗词插件接口文档 2 | 3 | >本接口文档仅供插件开发者阅读,如果你想学习如何开发此插件,请阅读 4 | [插件开发](http://doc.cms.7yue.pro/lin/server/plugin_create.html) 5 | 6 | ## 获取所有诗词接口 7 | URL: 8 | >GET http://localhost:5000/plugin/poem/all 9 | 10 | Parameters: 11 | - count: (可选)获取的数量,最小1,最大100,默认5 12 | - author: (可选)按照作者查找,作者列表从获取所有作者API获取 13 | 14 | Response: 15 | ```json 16 | [ 17 | { 18 | "author": "欧阳修", 19 | "content": [ 20 | [ 21 | "去年元夜时", 22 | "花市灯如昼", 23 | "月上柳梢头", 24 | "人约黄昏后" 25 | ], 26 | [ 27 | "今年元夜时", 28 | "月与灯依旧", 29 | "不见去年人", 30 | "泪湿春衫袖" 31 | ] 32 | ], 33 | "create_time": 1549438754000, 34 | "dynasty": "宋代", 35 | "id": 1, 36 | "image": "", 37 | "title": "生查子·元夕" 38 | }, 39 | { 40 | "author": "苏轼", 41 | "content": [ 42 | [ 43 | "一别都门三改火", 44 | "天涯踏尽红尘", 45 | "依然一笑作春温", 46 | "无波真古井", 47 | "有节是秋筠" 48 | ], 49 | [ 50 | "惆怅孤帆连夜发", 51 | "送行淡月微云", 52 | "尊前不用翠眉颦", 53 | "人生如逆旅", 54 | "我亦是行人" 55 | ] 56 | ], 57 | "create_time": 1549438754000, 58 | "dynasty": "宋代", 59 | "id": 2, 60 | "image": "", 61 | "title": "临江仙·送钱穆父" 62 | } 63 | ] 64 | ``` 65 | 66 | Response_description: 67 | - author: 作者 68 | - content: 内容。是一个二维数组,第一维数组用来区分词的每一阙(如果是古诗,那么第一维数组中只有一个元素),第二维数组用来区分古诗词的每一句。 69 | - create_time: 创建时间 70 | - dynasty: 作者所属朝代 71 | - id: id号码 72 | - image: 配图 73 | - title: 标题 74 | 75 | 76 | ## 获取所有作者接口 77 | URL: 78 | >GET http://localhost:5000/plugin/poem/authors 79 | 80 | Response: 81 | ```json 82 | [ 83 | "元稹", 84 | "晏殊", 85 | "欧阳修", 86 | "纳兰性德", 87 | "苏轼", 88 | "薛涛" 89 | ] 90 | ``` 91 | -------------------------------------------------------------------------------- /easy-test-vue/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import routes from './routes' 4 | import store from '../store' 5 | import appConfig from '@/config/index' 6 | import Util from '@/lin/utils/util' 7 | 8 | Vue.use(Router) 9 | 10 | // 判断是否需要登录访问, 配置位于 config 文件夹 11 | let isLoginRequired = routeName => { 12 | // 首次执行时缓存配置 13 | let { notLoginRoute } = appConfig 14 | const notLoginMark = {} 15 | 16 | // 构建标记对象 17 | if (Array.isArray(notLoginRoute)) { 18 | for (let i = 0; i < notLoginRoute.length; i += 1) { 19 | notLoginMark[notLoginRoute[i].toString()] = true 20 | } 21 | } 22 | 23 | notLoginRoute = null // 释放内存 24 | 25 | // 重写初始化函数 26 | isLoginRequired = name => { 27 | if (!name) { 28 | return true 29 | } 30 | // 处理 Symbol 类型 31 | const target = typeof name === 'symbol' ? name.description : name 32 | return !notLoginMark[target] 33 | } 34 | 35 | return isLoginRequired(routeName) 36 | } 37 | 38 | const router = new Router({ 39 | // mode: 'history', 40 | scrollBehavior: () => ({ 41 | y: 0, 42 | }), 43 | base: process.env.BASE_URL, 44 | routes, 45 | }) 46 | 47 | router.beforeEach((to, from, next) => { 48 | // 登录验证 49 | if (isLoginRequired(to.name) && !store.state.logined) { 50 | next({ path: '/login' }) 51 | return 52 | } 53 | 54 | // TODO: tab 模式重复点击验证 55 | 56 | // 权限验证 57 | if (store && store.state && store.getters) { 58 | const { auths, user } = store.getters 59 | if (to.path !== '/about' && !Util.hasPermission(auths, to.meta, user)) { 60 | Vue.prototype.$notify({ 61 | title: '无权限', 62 | dangerouslyUseHTMLString: true, 63 | message: '您无此页面的权限哟', 64 | }) 65 | next({ path: '/about' }) 66 | return 67 | } 68 | } 69 | 70 | // 路由发生变化重新计时 71 | Vue.prototype.$_lin_jump() 72 | 73 | // 路由发生变化修改页面title 74 | if (to.meta.title) { 75 | document.title = to.meta.title 76 | } 77 | 78 | next() 79 | }) 80 | 81 | export default router 82 | -------------------------------------------------------------------------------- /easy-test-vue/src/components/layout/MenuTab.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ tab.title | filterTitle }} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 43 | 44 | 85 | -------------------------------------------------------------------------------- /easy-test-mini/pages/case/case.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | {{item.name}} 16 | GET 17 | POST 18 | PUT 19 | DELETE 20 | 21 | {{item.url}} 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /easy-test-flask/app/api/v1/mock.py: -------------------------------------------------------------------------------- 1 | from flask import jsonify, current_app 2 | from lin import route_meta, group_required 3 | from lin.exception import Success 4 | from lin.redprint import Redprint 5 | 6 | from app.models.mock import Mock 7 | from app.validators.MockForm import MockForm, MockSearchForm 8 | 9 | mock_api = Redprint('mock') 10 | 11 | 12 | @mock_api.route('', methods=['POST']) 13 | @route_meta('mock', module='mock管理') 14 | @group_required 15 | def add_mock(): 16 | form = MockForm().validate_for_api() 17 | mock = Mock(form.method.data, form.url.data, form.requestHeader.data, form.requestBody.data, 18 | form.responseHeader.data, form.responseBody.data, form.statusCode.data, form.msg.data) 19 | mock.new_mock() 20 | 21 | return Success(msg='新增mock成功') 22 | 23 | 24 | @mock_api.route('/', methods=['PUT']) 25 | @route_meta('mock', module='mock管理') 26 | @group_required 27 | def edit_mock(mid): 28 | form = MockForm().validate_for_api() 29 | mock = Mock(form.method.data, form.url.data, form.requestHeader.data, form.requestBody.data, 30 | form.responseHeader.data, form.responseBody.data, form.statusCode.data, form.msg.data) 31 | mock.mid = mid 32 | mock.edit_mock() 33 | 34 | return Success(msg='修改mock成功') 35 | 36 | 37 | @mock_api.route('/', methods=['DELETE']) 38 | @route_meta('mock', module='mock管理') 39 | @group_required 40 | def delete_mock(mid): 41 | mock = Mock() 42 | mock.mid = mid 43 | mock.delete_mock() 44 | 45 | return Success(msg='删除mock成功') 46 | 47 | 48 | @mock_api.route('', methods=['GET']) 49 | @route_meta('mock', module='mock管理') 50 | @group_required 51 | def search_mock(): 52 | form = MockSearchForm().validate_for_api() 53 | mocks = Mock.search_mock(form.mid.data, form.url.data) 54 | 55 | return jsonify(mocks) 56 | 57 | 58 | @mock_api.route('/server', methods=['GET']) 59 | @route_meta('mock', module='mock管理') 60 | @group_required 61 | def mock_server(): 62 | # 获取mock server 63 | result = { 64 | 'server': current_app.config.get('MOCK_SERVER') 65 | } 66 | 67 | return result 68 | -------------------------------------------------------------------------------- /easy-test-vue/src/components/layout/BackTop.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 62 | 63 | 81 | -------------------------------------------------------------------------------- /easy-test-mini/project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目配置文件,详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", 3 | "packOptions": { 4 | "ignore": [ 5 | { 6 | "value": ".eslintrc.js", 7 | "type": "file" 8 | } 9 | ], 10 | "include": [] 11 | }, 12 | "setting": { 13 | "urlCheck": false, 14 | "es6": true, 15 | "enhance": true, 16 | "postcss": true, 17 | "preloadBackgroundData": false, 18 | "minified": true, 19 | "newFeature": false, 20 | "coverView": true, 21 | "nodeModules": true, 22 | "autoAudits": false, 23 | "showShadowRootInWxmlPanel": true, 24 | "scopeDataCheck": false, 25 | "uglifyFileName": false, 26 | "checkInvalidKey": true, 27 | "checkSiteMap": true, 28 | "uploadWithSourceMap": true, 29 | "compileHotReLoad": false, 30 | "lazyloadPlaceholderEnable": false, 31 | "useMultiFrameRuntime": true, 32 | "useApiHook": true, 33 | "useApiHostProcess": true, 34 | "babelSetting": { 35 | "ignore": [], 36 | "disablePlugins": [], 37 | "outputPath": "" 38 | }, 39 | "useIsolateContext": false, 40 | "userConfirmedBundleSwitch": false, 41 | "packNpmManually": false, 42 | "packNpmRelationList": [], 43 | "minifyWXSS": true, 44 | "disableUseStrict": false, 45 | "minifyWXML": true, 46 | "showES6CompileOption": false, 47 | "useCompilerPlugins": false, 48 | "ignoreUploadUnusedFiles": true, 49 | "useStaticServer": true 50 | }, 51 | "compileType": "miniprogram", 52 | "libVersion": "2.21.4", 53 | "appid": "wx8675a570d3445569", 54 | "projectname": "easy-test-mini", 55 | "condition": { 56 | "search": { 57 | "list": [] 58 | }, 59 | "conversation": { 60 | "list": [] 61 | }, 62 | "game": { 63 | "list": [] 64 | }, 65 | "plugin": { 66 | "list": [] 67 | }, 68 | "gamePlugin": { 69 | "list": [] 70 | }, 71 | "miniprogram": { 72 | "list": [] 73 | } 74 | }, 75 | "editorSetting": { 76 | "tabIndent": "insertSpaces", 77 | "tabSize": 2 78 | } 79 | } -------------------------------------------------------------------------------- /easy-test-vue/src/lin/directives/ripple.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | /* eslint-disable func-names */ 4 | Vue.directive('ripple', { 5 | inserted(el, binding) { 6 | const cl = Array.from(el.classList) 7 | if (cl.includes('disabled')) { 8 | return 9 | } 10 | 11 | let background = 'rgba(0,0,0,.3)' 12 | el.setAttribute('ripple', 'ripple') 13 | /* eslint no-param-reassign: 0 */ 14 | el.style.position = 'relative' 15 | if (binding.value) { 16 | background = binding.value 17 | } 18 | 19 | const isMobile = window.navigator.userAgent.match(/Mobile/) && window.navigator.userAgent.match(/Mobile/)[0] === 'Mobile' 20 | const event = isMobile ? 'touchstart' : 'click' 21 | 22 | el.addEventListener(event, function (e) { 23 | // e.preventDefault() 24 | const button = el 25 | const rect = button.getBoundingClientRect() 26 | const btnWidth = rect.width 27 | let posMouseX = 0 28 | let posMouseY = 0 29 | 30 | if (isMobile) { 31 | posMouseX = e.changedTouches[0].pageX - rect.left 32 | posMouseY = e.changedTouches[0].pageY - rect.top 33 | } else { 34 | posMouseX = e.x - rect.left 35 | posMouseY = e.y - rect.top 36 | } 37 | 38 | const baseCSS = ` 39 | position: absolute; width: ${btnWidth * 2}px; height: ${btnWidth * 2}px; transition: all linear 1000ms; 40 | transition-timing-function:cubic-bezier(0.250, 0.460, 0.450, 0.940);border-radius: 50%; 41 | background: ${background}; top:${posMouseY - btnWidth}px; left:${posMouseX - btnWidth}px; 42 | pointer-events: none; transform:scale(0)` 43 | 44 | const rippleEffect = document.createElement('span') 45 | rippleEffect.style.cssText = baseCSS 46 | 47 | button.style.overflow = 'hidden' 48 | this.appendChild(rippleEffect) 49 | 50 | setTimeout(() => { 51 | rippleEffect.style.cssText = `${baseCSS}transform:scale(1); opacity: 0;` 52 | }, 10) 53 | 54 | setTimeout(() => { 55 | rippleEffect.remove() 56 | }, 1000) 57 | }) 58 | }, 59 | }) 60 | 61 | export default Vue.directive('ripple') 62 | -------------------------------------------------------------------------------- /easy-test-flask/vendor/plugin_generator.py: -------------------------------------------------------------------------------- 1 | """ 2 | :copyright: © 2019 by the Lin team. 3 | :license: MIT, see LICENSE for more details. 4 | """ 5 | import argparse 6 | import os 7 | 8 | banner = """ 9 | \""" 10 | :copyright: © 2019 by the Lin team. 11 | :license: MIT, see LICENSE for more details. 12 | \""" 13 | """ 14 | 15 | controller = """ 16 | from lin.redprint import Redprint 17 | 18 | {0}_api = Redprint("{0}") 19 | 20 | 21 | @{0}_api.route("/", methods=["GET"]) 22 | def test(): 23 | return "hi, guy!" 24 | """ 25 | 26 | init = """ 27 | from .controller import {0}_api 28 | """ 29 | 30 | info = """ 31 | __name__ = '{0}' 32 | __version__ = '0.1.0' 33 | __author__ = 'Team Lin' 34 | """ 35 | 36 | readme = """# {0}""" 37 | 38 | 39 | def create_plugin(name: str): 40 | cmd = os.getcwd() 41 | plugins_path = os.path.join(cmd, "app/plugins") 42 | plugindir = os.path.join(plugins_path, name) 43 | os.mkdir(plugindir) 44 | 45 | open(os.path.join(plugindir, "config.py"), mode="x", encoding="utf-8") 46 | open(os.path.join(plugindir, "requirements.txt"), mode="x", encoding="utf-8") 47 | 48 | with open(os.path.join(plugindir, "info.py"), mode="x", encoding="utf-8") as f: 49 | f.write(banner + info.format(name)) 50 | 51 | with open(os.path.join(plugindir, "README.md"), mode="x", encoding="utf-8") as f: 52 | f.write(readme.format(name)) 53 | 54 | appdir = os.path.join(plugindir, "app") 55 | os.mkdir(appdir) 56 | 57 | with open(os.path.join(appdir, "__init__.py"), mode="x", encoding="utf-8") as f: 58 | f.write(banner + init.format(name)) 59 | 60 | with open(os.path.join(appdir, "controller.py"), mode="x", encoding="utf-8") as f: 61 | f.write(banner + controller.format(name)) 62 | 63 | open(os.path.join(appdir, "model.py"), mode="x", encoding="utf-8") 64 | 65 | 66 | if __name__ == '__main__': 67 | parser = argparse.ArgumentParser(usage="it's usage tip.", description="help info.") 68 | parser.add_argument("-n", "--name", default="tpl", help="the name of plugin", dest="name") 69 | args = parser.parse_args() 70 | name = args.name 71 | create_plugin(name) 72 | -------------------------------------------------------------------------------- /easy-test-vue/script/lib/install-dep.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk') 2 | const semver = require('semver') 3 | const exec = require('./exec-promise') 4 | 5 | // 获取当前安装包版本 6 | const getLocalVersion = async (pkg, isDev) => { 7 | let lsInfo 8 | try { 9 | lsInfo = JSON.parse(await exec(`npm ls ${pkg} --json --depth=0 ${isDev ? '--dev' : '--prod'}`)) 10 | if (lsInfo.dependencies) { 11 | lsInfo = lsInfo.dependencies[pkg].version 12 | } 13 | } catch (e) { 14 | lsInfo = false 15 | } 16 | return lsInfo 17 | } 18 | 19 | // pkg: 需要安装的包名, isDev: 是否安装到 dev 依赖, pPackage: 项目的 package 文件 20 | const installFuc = async (pkg, version, pPackage = {}, isDev = false) => { 21 | if (!pkg) { 22 | throw new Error('未传入需要安装的包名') 23 | } 24 | 25 | // 参数兼容性处理 26 | const originPkg = {} 27 | originPkg.dependencies = pPackage.dependencies || {} 28 | originPkg.devDependencies = pPackage.devDependencies || {} 29 | 30 | const key = isDev ? 'devDependencies' : 'dependencies' 31 | 32 | // 获取当前安装包版本 33 | let v = await getLocalVersion(pkg, isDev) 34 | 35 | // 如果未检测到安装包版本, 属于异常, 则本地先安装一下 36 | if (v) { 37 | // 检测当前已安装版本是否符合要求 38 | if (semver.satisfies(v, version)) { 39 | console.log(`当前已存在依赖 ${pkg}@${v}`) 40 | return true 41 | } 42 | } else { 43 | console.log(chalk.yellow(`安装依赖 ${pkg}@${version}`)) 44 | await exec(`npm install ${isDev ? '--save-dev' : ''} ${pkg}@${version}`) 45 | v = await getLocalVersion(pkg, isDev) 46 | } 47 | 48 | // 本地项目本身无该包 49 | if (!originPkg[key][pkg]) { 50 | return true 51 | } 52 | 53 | // 检测当前已安装版本是否符合要求 54 | if (semver.satisfies(v, originPkg[key][pkg])) { 55 | console.log(`当前已存在依赖 ${pkg}@${v}`) 56 | return true 57 | } 58 | 59 | // 不符合要求则按照项目要求回滚 60 | await exec(`npm install ${isDev ? '--save-dev' : ''} ${pkg}@${originPkg[key][pkg]}`) 61 | 62 | // 检测回滚后能否符合要求 63 | v = await getLocalVersion(pkg, isDev) 64 | if (semver.satisfies(v, version)) { 65 | console.log(`已更新依赖 ${pkg}@${v}`) 66 | return true 67 | } 68 | 69 | // 回滚后还是不符合, 说明两版本要求冲突 70 | throw new Error(`依赖包 ${pkg} 与本地版本有冲突, 版本要求是 ${version}, 本地项目要求是 ${originPkg[key][pkg]}`) 71 | } 72 | 73 | module.exports = installFuc 74 | -------------------------------------------------------------------------------- /easy-test-vue/src/config/stage/case.js: -------------------------------------------------------------------------------- 1 | const caseRouter = { 2 | route: null, 3 | name: null, 4 | title: '用例管理', 5 | type: 'folder', 6 | icon: 'iconfont icon-caseStore', 7 | filePath: 'views/case/', 8 | order: 3, 9 | inNav: true, 10 | permission: ['测试用例'], 11 | children: [ 12 | { 13 | title: '用例列表', 14 | type: 'view', 15 | name: 'CaseList', 16 | route: '/case/case/list', 17 | filePath: 'views/case/case/CaseList.vue', 18 | inNav: true, 19 | icon: 'iconfont icon-list', 20 | keepAlive: true, 21 | permission: ['测试用例'], 22 | }, 23 | { 24 | title: '添加用例', 25 | type: 'view', 26 | inNav: true, 27 | route: '/case/case/add', 28 | icon: 'iconfont icon-add', 29 | name: 'CaseAdd', 30 | filePath: 'views/case/case/CaseAddOrEdit.vue', 31 | permission: ['新增用例'], 32 | }, 33 | { 34 | title: '修改记录', 35 | type: 'view', 36 | inNav: true, 37 | route: '/case/modify/log', 38 | icon: 'iconfont icon-modifyRecord', 39 | name: 'CaseAdd', 40 | keepAlive: true, 41 | filePath: 'views/case/modify/ModifyLog.vue', 42 | permission: ['修改记录'], 43 | }, 44 | { 45 | route: '/case/group/list', 46 | name: null, 47 | title: '分组管理', 48 | type: 'tab', // 取 route 为默认加载页 49 | icon: 'iconfont icon-fenzu', 50 | filePath: 'views/case/group', 51 | inNav: true, 52 | permission: ['用例分组'], 53 | children: [ 54 | { 55 | route: '/case/group/list', 56 | type: 'view', 57 | name: 'groupList', 58 | inNav: true, 59 | filePath: 'views/case/group/GroupList.vue', 60 | title: '分组列表', 61 | icon: 'iconfont icon-fenzu', 62 | keepAlive: false, 63 | }, 64 | { 65 | route: '/case/group/add', 66 | type: 'view', 67 | name: 'groupAdd', 68 | filePath: 'views/case/group/GroupAdd.vue', 69 | inNav: true, 70 | title: '添加分组', 71 | icon: 'iconfont icon-add', 72 | keepAlive: false, 73 | }, 74 | ], 75 | }, 76 | ], 77 | } 78 | 79 | export default caseRouter 80 | -------------------------------------------------------------------------------- /easy-test-flask/app/libs/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | :copyright: © 2019 by the Lin team. 3 | :license: MIT, see LICENSE for more details. 4 | """ 5 | 6 | import re 7 | import time 8 | 9 | import pypinyin 10 | from flask import current_app, jsonify, request 11 | from lin.exception import ParameterException 12 | 13 | 14 | def get_timestamp(fmt='%Y-%m-%d %H:%M:%S'): 15 | return time.strftime(fmt, time.localtime(time.time())) 16 | 17 | 18 | def get_count_from_query(): 19 | count_default = current_app.config.get('COUNT_DEFAULT') 20 | count = int(request.args.get('count', count_default if count_default else 1)) 21 | return count 22 | 23 | 24 | def get_page_from_query(): 25 | page_default = current_app.config.get('PAGE_DEFAULT') 26 | page = int(request.args.get('page', page_default if page_default else 0)) 27 | return page 28 | 29 | 30 | def paginate(): 31 | _count = get_count_from_query() 32 | count = 15 if _count >= 15 else _count 33 | start = get_page_from_query() * count 34 | if start < 0 or count < 0: 35 | raise ParameterException() 36 | return start, count 37 | 38 | 39 | def camel2line(camel: str): 40 | p = re.compile(r'([a-z]|\d)([A-Z])') 41 | line = re.sub(p, r'\1_\2', camel).lower() 42 | return line 43 | 44 | 45 | def json_res(**kwargs): 46 | """ 47 | 将所有传入的关键字参数转变为dict后序列化为json格式的response 48 | count, items, page, total, total_page ... 49 | """ 50 | return jsonify(kwargs) 51 | 52 | 53 | def paging(paginate): 54 | return { 55 | 'data': paginate.items, 56 | 'page': paginate.page, 57 | 'pages': paginate.pages, 58 | 'count': paginate.per_page, 59 | 'total': paginate.total 60 | } 61 | 62 | 63 | # 汉字转拼音 64 | def pinyin(word): 65 | s = '' 66 | for i in pypinyin.pinyin(word, style=pypinyin.NORMAL): 67 | s += ''.join(i) 68 | return s 69 | 70 | 71 | # 生成首字母分组列表 72 | def group_by_initials(): 73 | letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 74 | 'V', 'W', 'X', 'Y', 'Z'] 75 | users = [] 76 | for letter in letters: 77 | users.append({'name': letter, 'users': []}) 78 | 79 | return users, letters 80 | -------------------------------------------------------------------------------- /easy-test-vue/src/config/stage/admin.js: -------------------------------------------------------------------------------- 1 | const adminRouter = { 2 | route: null, 3 | name: null, 4 | title: '权限管理', 5 | type: 'folder', 6 | icon: 'iconfont icon-huiyuanguanli', 7 | filePath: 'views/admin/', 8 | order: null, 9 | inNav: true, 10 | permission: ['超级管理员独有权限'], 11 | children: [ 12 | { 13 | route: '/admin/user/list', 14 | name: null, 15 | title: '用户管理', 16 | type: 'folder', // 取 route 为默认加载页 17 | icon: 'iconfont icon-huiyuanguanli', 18 | filePath: 'views/admin/user/', 19 | inNav: true, 20 | children: [ 21 | { 22 | title: '用户列表', 23 | type: 'view', 24 | name: 'userList', 25 | route: '/admin/user/list', 26 | filePath: 'views/admin/user/UserList.vue', 27 | inNav: true, 28 | icon: 'iconfont icon-huiyuanguanli', 29 | permission: ['超级管理员独有权限'], 30 | }, 31 | { 32 | title: '添加用户', 33 | type: 'view', 34 | inNav: true, 35 | route: '/admin/user/add', 36 | icon: 'iconfont icon-add', 37 | name: 'userAdd', 38 | filePath: 'views/admin/user/UserAdd.vue', 39 | permission: ['超级管理员独有权限'], 40 | }, 41 | ], 42 | }, 43 | { 44 | route: '/admin/group/list', 45 | name: null, 46 | title: '分组管理', 47 | type: 'tab', // 取 route 为默认加载页 48 | icon: null, 49 | filePath: 'views/admin/group', 50 | inNav: true, 51 | children: [ 52 | { 53 | route: '/admin/group/list', 54 | type: 'view', 55 | name: 'groupList', 56 | inNav: true, 57 | filePath: 'views/admin/group/GroupList.vue', 58 | title: '分组列表', 59 | icon: 'iconfont icon-huiyuanguanli', 60 | permission: ['超级管理员独有权限'], 61 | }, 62 | { 63 | route: '/admin/group/add', 64 | type: 'view', 65 | name: 'groupAdd', 66 | filePath: 'views/admin/group/GroupAdd.vue', 67 | inNav: true, 68 | title: '添加分组', 69 | icon: 'iconfont icon-add', 70 | permission: ['超级管理员独有权限'], 71 | }, 72 | ], 73 | }, 74 | ], 75 | } 76 | 77 | export default adminRouter 78 | -------------------------------------------------------------------------------- /easy-test-mini/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index", 4 | "pages/logs/logs", 5 | "pages/home/home", 6 | "pages/test/test", 7 | "pages/case/case", 8 | "pages/case-detail/case-detail" 9 | ], 10 | "window": { 11 | "backgroundTextStyle": "light", 12 | "navigationBarBackgroundColor": "#2c61b4", 13 | "navigationBarTitleText": "easy-test", 14 | "navigationBarTextStyle": "white" 15 | }, 16 | "style": "v2", 17 | "sitemapLocation": "sitemap.json", 18 | "usingComponents": { 19 | "l-button": "/miniprogram_npm/lin-ui/button/index", 20 | "l-dialog": "/miniprogram_npm/lin-ui/dialog/index", 21 | "l-toast": "/miniprogram_npm/lin-ui/toast/index", 22 | "l-input": "/miniprogram_npm/lin-ui/input/index", 23 | "l-tab-bar": "/miniprogram_npm/lin-ui/tab-bar/index", 24 | "l-grid": "/miniprogram_npm/lin-ui/grid/index", 25 | "l-grid-item": "/miniprogram_npm/lin-ui/grid-item/index", 26 | "l-card": "/miniprogram_npm/lin-ui/card/index", 27 | "l-popup": "/miniprogram_npm/lin-ui/popup/index", 28 | "l-list": "/miniprogram_npm/lin-ui/list/index", 29 | "l-status-show": "/miniprogram_npm/lin-ui/status-show/index", 30 | "l-message": "/miniprogram_npm/lin-ui/message/index", 31 | "l-search-bar": "/miniprogram_npm/lin-ui/search-bar/index", 32 | "l-icon": "/miniprogram_npm/lin-ui/icon/index", 33 | "l-loading": "/miniprogram_npm/lin-ui/loading/index", 34 | "l-tag": "/miniprogram_npm/lin-ui/tag/index", 35 | "l-loadmore": "/miniprogram_npm/lin-ui/loadmore/index", 36 | "ec-canvas": "/ec-canvas/ec-canvas" 37 | }, 38 | "tabBar": { 39 | "borderStyle": "white", 40 | "selectedColor": "#2c61b4", 41 | "color": "#666666", 42 | "list": [ 43 | { 44 | "pagePath": "pages/home/home", 45 | "text": "总览", 46 | "iconPath": "/images/tab/home.png", 47 | "selectedIconPath": "/images/tab/home-select.png" 48 | }, 49 | { 50 | "pagePath": "pages/test/test", 51 | "text": "测试", 52 | "iconPath": "/images/tab/test.png", 53 | "selectedIconPath": "/images/tab/test-select.png" 54 | }, 55 | { 56 | "pagePath": "pages/case/case", 57 | "text": "用例", 58 | "iconPath": "/images/tab/case.png", 59 | "selectedIconPath": "/images/tab/case-select.png" 60 | } 61 | ] 62 | }, 63 | "useExtendedLib": { 64 | "weui": true 65 | } 66 | } -------------------------------------------------------------------------------- /easy-test-mini/iconfont.wxss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'iconfont'; /* Project id 1661757 */ 3 | src: url('//at.alicdn.com/t/font_1661757_p66w2jo77s.woff2?t=1643424185538') format('woff2'), 4 | url('//at.alicdn.com/t/font_1661757_p66w2jo77s.woff?t=1643424185538') format('woff'), 5 | url('//at.alicdn.com/t/font_1661757_p66w2jo77s.ttf?t=1643424185538') format('truetype'); 6 | } 7 | .iconfont { 8 | font-family: "iconfont" !important; 9 | font-size: 16px; 10 | font-style: normal; 11 | -webkit-font-smoothing: antialiased; 12 | -moz-osx-font-smoothing: grayscale; 13 | } 14 | 15 | .l-icon-robot:before { 16 | content: "\e657"; 17 | } 18 | 19 | .l-icon-var:before { 20 | content: "\e610"; 21 | } 22 | 23 | .l-icon-audioOpen:before { 24 | content: "\e64c"; 25 | } 26 | 27 | .l-icon-audioClose:before { 28 | content: "\e747"; 29 | } 30 | 31 | .l-icon-HTMLreport:before { 32 | content: "\e63f"; 33 | } 34 | 35 | .l-icon-analysis:before { 36 | content: "\e618"; 37 | } 38 | 39 | .l-icon-result:before { 40 | content: "\e7c2"; 41 | } 42 | 43 | .l-icon-list:before { 44 | content: "\e645"; 45 | } 46 | 47 | .l-icon-manager:before { 48 | content: "\e609"; 49 | } 50 | 51 | .l-icon-debug:before { 52 | content: "\e60a"; 53 | } 54 | 55 | .l-icon-mock:before { 56 | content: "\e60c"; 57 | } 58 | 59 | .l-icon-execute:before { 60 | content: "\e63e"; 61 | } 62 | 63 | .l-icon-modifyRecord:before { 64 | content: "\e60b"; 65 | } 66 | 67 | .l-icon-executeRecord:before { 68 | content: "\e61f"; 69 | } 70 | 71 | .l-icon-executeDetail:before { 72 | content: "\e669"; 73 | } 74 | 75 | .l-icon-caseLog:before { 76 | content: "\e663"; 77 | } 78 | 79 | .l-icon-config:before { 80 | content: "\e63c"; 81 | } 82 | 83 | .l-icon-corn:before { 84 | content: "\e651"; 85 | } 86 | 87 | .l-icon-mine:before { 88 | content: "\e687"; 89 | } 90 | 91 | .l-icon-postman:before { 92 | content: "\ec5d"; 93 | } 94 | 95 | .l-icon-Info2:before { 96 | content: "\e608"; 97 | } 98 | 99 | .l-icon-caseGroup:before { 100 | content: "\e606"; 101 | } 102 | 103 | .l-icon-caseStore:before { 104 | content: "\e607"; 105 | } 106 | 107 | .l-icon-fenzu:before { 108 | content: "\e66c"; 109 | } 110 | 111 | .l-icon-info:before { 112 | content: "\e650"; 113 | } 114 | 115 | .l-icon-shanzi:before { 116 | content: "\e6c7"; 117 | } -------------------------------------------------------------------------------- /easy-test-flask/app/config/secure.py: -------------------------------------------------------------------------------- 1 | """ 2 | :copyright: © 2019 by the Lin team. 3 | :license: MIT, see LICENSE for more details. 4 | """ 5 | 6 | # 安全性配置 7 | import os 8 | from apscheduler.jobstores.mongodb import MongoDBJobStore 9 | 10 | from app.config.setting import BaseConfig 11 | 12 | 13 | class DevelopmentSecure(BaseConfig): 14 | """ 15 | 开发环境安全性配置 16 | """ 17 | SQLALCHEMY_DATABASE_URI = 'mysql+cymysql://root:root@127.0.0.1:8081/easy-test' 18 | 19 | SQLALCHEMY_ECHO = False 20 | 21 | SECRET_KEY = '\x88W\xf09\x91\x07\x98\x85\x87\x96\xb0A\xc68\xf9\xecJJU\x12\xc5V\xbe\x8b\xef\xd7\xd8\xd3\xe6\x95*6' 22 | 23 | MONGO_URI = 'mongodb://root:mongo2020@127.0.0.1:8090/easyTest?authSource=admin' 24 | 25 | CELERY_BROKER_URL = 'amqp://admin:ftlb2000@127.0.0.1:8083/my_vhost' 26 | 27 | # scheduler 28 | SCHEDULER_JOBSTORES = { 29 | 'default': MongoDBJobStore(host='127.0.0.1', port=8090, username='root', password='mongo2020') 30 | } 31 | 32 | # mail 33 | MAIL_SERVER = 'smtp.sina.cn' 34 | MAIL_PORT = 465 35 | MAIL_USE_TLS = False 36 | MAIL_USE_SSL = True 37 | MAIL_DEFAULT_SENDER = '23456789@sina.cn' 38 | MAIL_USERNAME = '23456789@sina.cn' 39 | MAIL_PASSWORD = 'sdf62f4e7fb' 40 | 41 | # mock server 42 | MOCK_SERVER = 'http://127.0.0.1:5000' 43 | 44 | API_SERVER = 'http://127.0.0.1:5000' 45 | 46 | SITE_DOMAIN = 'http://127.0.0.1:5000' 47 | 48 | 49 | class ProductionSecure(BaseConfig): 50 | """ 51 | 生产环境安全性配置 52 | """ 53 | SQLALCHEMY_DATABASE_URI = 'mysql+cymysql://root:root@mysql:3306/easy-test' 54 | 55 | SQLALCHEMY_ECHO = False 56 | 57 | SECRET_KEY = '\x88W\xf09\x91\x07\x98\x85\x87\x96\xb0A\xc68\xf9\xecJJU\x12\xc5V\xbe\x8b\xef\xd7\xd8\xd3\xe6\x95*6' 58 | 59 | MONGO_URI = 'mongodb://root:mongo2020@mongo:27017/easyTest?authSource=admin' 60 | 61 | CELERY_BROKER_URL = 'amqp://admin:ftlb2000@rabbitmq:5672/my_vhost' 62 | 63 | # scheduler 64 | SCHEDULER_JOBSTORES = { 65 | 'default': MongoDBJobStore(host='mongo', port=27017, username='root', password='mongo2020') 66 | } 67 | 68 | # mail 69 | MAIL_SERVER = 'smtp.sina.cn' 70 | MAIL_PORT = 465 71 | MAIL_USE_TLS = False 72 | MAIL_USE_SSL = True 73 | MAIL_DEFAULT_SENDER = '12345675@sina.cn' 74 | MAIL_USERNAME = '15234xxxxx15@sina.cn' 75 | MAIL_PASSWORD = '63xx7a66xx7fb' 76 | 77 | # mock server 78 | MOCK_SERVER = 'http://api:5000' 79 | 80 | API_SERVER = 'http://api:5000' 81 | 82 | # 前端访问地址 83 | SITE_DOMAIN = os.getenv('SITE_DOMAIN') 84 | -------------------------------------------------------------------------------- /easy-test-vue/src/plugins/LinCmsUi/models/movie.js: -------------------------------------------------------------------------------- 1 | import { movieList } from '../simulations/movie' 2 | 3 | class Movie { 4 | getTop250(start = 0, count = 20) { 5 | const arr = [] 6 | const tempList = movieList.slice() 7 | const currentList = tempList.splice(start, count) 8 | currentList.forEach((element, index) => { 9 | const tempCasts = [] 10 | const tempDirectors = [] 11 | element.casts.forEach(el => { 12 | tempCasts.push(el.name) 13 | }) 14 | element.directors.forEach(el => { 15 | tempDirectors.push(el.name) 16 | }) 17 | 18 | arr.push({ 19 | title: element.title, 20 | originalTitle: element.original_title, 21 | year: element.year, 22 | rating: element.rating.average, 23 | casts: tempCasts.join('/'), 24 | directors: tempDirectors.join('/'), 25 | genres: element.genres.join('/'), 26 | rank: index + 1 + start, 27 | sorting: 50, 28 | recommend: 0, 29 | remark: '这是一部不错的电影', 30 | editFlag: false, 31 | thumb: element.thumb 32 | ? element.thumb 33 | : 'https://consumerminiaclprd01.blob.core.chinacloudapi.cn/miniappbackground/sfgmember/lin/270-400.png', 34 | }) 35 | }) 36 | 37 | return arr 38 | } 39 | 40 | getDataByQuery(query = '') { 41 | const arr = [] 42 | for (let index = 0; index < movieList.length; index++) { 43 | const element = movieList[index] 44 | 45 | if (element.title.match(query)) { 46 | const tempCasts = [] 47 | const tempDirectors = [] 48 | element.casts.forEach(el => { 49 | tempCasts.push(el.name) 50 | }) 51 | element.directors.forEach(el => { 52 | tempDirectors.push(el.name) 53 | }) 54 | 55 | arr.push({ 56 | title: element.title, 57 | originalTitle: element.original_title, 58 | year: element.year, 59 | rating: element.rating.average, 60 | casts: tempCasts.join('/'), 61 | directors: tempDirectors.join('/'), 62 | genres: element.genres.join('/'), 63 | rank: index + 1, 64 | sorting: 50, 65 | recommend: 0, 66 | remark: '这是一部不错的电影', 67 | editFlag: false, 68 | thumb: element.thumb 69 | ? element.thumb 70 | : 'https://consumerminiaclprd01.blob.core.chinacloudapi.cn/miniappbackground/sfgmember/lin/270-400.png', 71 | }) 72 | } 73 | } 74 | 75 | return arr 76 | } 77 | } 78 | 79 | export default new Movie() 80 | -------------------------------------------------------------------------------- /easy-test-mini/pages/case-detail/case-detail.js: -------------------------------------------------------------------------------- 1 | // pages/case-detail/case-detail.js 2 | const app = getApp() 3 | Page({ 4 | 5 | /** 6 | * 页面的初始数据 7 | */ 8 | data: { 9 | detail: {} 10 | }, 11 | 12 | /** 13 | * 生命周期函数--监听页面加载 14 | */ 15 | onLoad: function (options) { 16 | this.getType() 17 | console.log(options) 18 | this.getDetail(options.cid) 19 | this.getCollect(options.cid) 20 | }, 21 | getCollect(cid) { 22 | var that = this; 23 | wx.request({ 24 | url: app.globalData.apiBase + '/v1/case/collect/' + cid, 25 | method: "GET", 26 | header: { 27 | "Authorization": "Bearer " + wx.getStorageSync('access_token') 28 | }, 29 | success: function(res){ 30 | that.setData({collect: res.data}) 31 | } 32 | }) 33 | }, 34 | getType() { 35 | var that = this; 36 | wx.request({ 37 | url: app.globalData.apiBase + '/v1/case/type', 38 | method: "GET", 39 | header: { 40 | "Authorization": "Bearer " + wx.getStorageSync('access_token') 41 | }, 42 | success: function(res){ 43 | that.setData({type: res.data}) 44 | } 45 | }) 46 | }, 47 | getDetail(cid) { 48 | var that = this; 49 | wx.request({ 50 | url: app.globalData.apiBase + `/v1/case?id=` + cid, 51 | method: "GET", 52 | header: { 53 | "Authorization": "Bearer " + wx.getStorageSync('access_token') 54 | }, 55 | success: function(res){ 56 | if (res.statusCode === 401 || res.statusCode === 422) { 57 | wx.setStorageSync('unauthorized', true) 58 | wx.redirectTo({ 59 | url: '/pages/index/index', 60 | }) 61 | } 62 | that.setData({detail: res.data.data[0]}) 63 | } 64 | }) 65 | }, 66 | /** 67 | * 生命周期函数--监听页面初次渲染完成 68 | */ 69 | onReady: function () { 70 | 71 | }, 72 | 73 | /** 74 | * 生命周期函数--监听页面显示 75 | */ 76 | onShow: function () { 77 | 78 | }, 79 | 80 | /** 81 | * 生命周期函数--监听页面隐藏 82 | */ 83 | onHide: function () { 84 | 85 | }, 86 | 87 | /** 88 | * 生命周期函数--监听页面卸载 89 | */ 90 | onUnload: function () { 91 | 92 | }, 93 | 94 | /** 95 | * 页面相关事件处理函数--监听用户下拉动作 96 | */ 97 | onPullDownRefresh: function () { 98 | 99 | }, 100 | 101 | /** 102 | * 页面上拉触底事件的处理函数 103 | */ 104 | onReachBottom: function () { 105 | 106 | }, 107 | 108 | /** 109 | * 用户点击右上角分享 110 | */ 111 | onShareAppMessage: function () { 112 | 113 | } 114 | }) -------------------------------------------------------------------------------- /easy-test-flask/app/api/v1/scheduler.py: -------------------------------------------------------------------------------- 1 | from flask import jsonify 2 | from lin import route_meta, group_required 3 | from lin.exception import Success 4 | from lin.redprint import Redprint 5 | 6 | from app.models.scheduler import Scheduler 7 | from app.validators.SchedulerForm import SchedulerForm, SchedulerSearchForm, SchedulerOperateForm, SchedulerEditForm 8 | 9 | scheduler_api = Redprint('scheduler') 10 | 11 | 12 | @scheduler_api.route('/add', methods=['POST']) 13 | @route_meta('新增定时任务', module='定时任务') 14 | @group_required 15 | def add_job(): 16 | form = SchedulerForm().validate_for_api() 17 | scheduler = Scheduler() 18 | scheduler.new_job(form.project.data, form.user.data, form.sendEmail.data, form.copyPerson.data, form.cron.data, 19 | form.emailStrategy.data) 20 | 21 | return Success('新增定时任务成功') 22 | 23 | 24 | @scheduler_api.route('/search', methods=['GET']) 25 | @route_meta('定时任务列表', module='定时任务') 26 | @group_required 27 | def search_jobs(): 28 | form = SchedulerSearchForm().validate_for_api() 29 | jobs = Scheduler.search_jobs(form.project.data, form.user.data, form.page.data, form.count.data) 30 | return jsonify(jobs) 31 | 32 | 33 | @scheduler_api.route('/start', methods=['GET']) 34 | @route_meta('启动定时任务', module='定时任务') 35 | @group_required 36 | def start_job(): 37 | form = SchedulerOperateForm().validate_for_api() 38 | scheduler = Scheduler.query.filter_by(scheduler_id=form.schedulerId.data).first() 39 | scheduler.start_job() 40 | 41 | return Success(msg='启动成功') 42 | 43 | 44 | @scheduler_api.route('/stop', methods=['GET']) 45 | @route_meta('停止定时任务', module='定时任务') 46 | @group_required 47 | def stop_job(): 48 | form = SchedulerOperateForm().validate_for_api() 49 | scheduler = Scheduler.query.filter_by(scheduler_id=form.schedulerId.data).first() 50 | scheduler.stop_job() 51 | 52 | return Success(msg='停止成功') 53 | 54 | 55 | @scheduler_api.route('/edit/', methods=['PUT']) 56 | @route_meta('编辑定时任务', module='定时任务') 57 | @group_required 58 | def edit_job(sid): 59 | form = SchedulerEditForm().validate_for_api() 60 | scheduler = Scheduler.query.filter_by(id=sid, delete_time=None).first() 61 | scheduler.edit_job(form.user.data, form.sendEmail.data, form.copyPerson.data, form.cron.data, 62 | form.emailStrategy.data) 63 | return Success(msg='修改成功') 64 | 65 | 66 | @scheduler_api.route('/remove/', methods=['DELETE']) 67 | @route_meta('删除定时任务', module='定时任务') 68 | @group_required 69 | def remove_job(sid): 70 | scheduler = Scheduler.query.filter_by(id=sid, delete_time=None).first() 71 | scheduler.remove_job() 72 | return Success(msg='删除成功') 73 | -------------------------------------------------------------------------------- /easy-test-mini/ec-canvas/wx-canvas.js: -------------------------------------------------------------------------------- 1 | export default class WxCanvas { 2 | constructor(ctx, canvasId, isNew, canvasNode) { 3 | this.ctx = ctx; 4 | this.canvasId = canvasId; 5 | this.chart = null; 6 | this.isNew = isNew 7 | if (isNew) { 8 | this.canvasNode = canvasNode; 9 | } 10 | else { 11 | this._initStyle(ctx); 12 | } 13 | 14 | // this._initCanvas(zrender, ctx); 15 | 16 | this._initEvent(); 17 | } 18 | 19 | getContext(contextType) { 20 | if (contextType === '2d') { 21 | return this.ctx; 22 | } 23 | } 24 | 25 | // canvasToTempFilePath(opt) { 26 | // if (!opt.canvasId) { 27 | // opt.canvasId = this.canvasId; 28 | // } 29 | // return wx.canvasToTempFilePath(opt, this); 30 | // } 31 | 32 | setChart(chart) { 33 | this.chart = chart; 34 | } 35 | 36 | attachEvent() { 37 | // noop 38 | } 39 | 40 | detachEvent() { 41 | // noop 42 | } 43 | 44 | _initCanvas(zrender, ctx) { 45 | zrender.util.getContext = function () { 46 | return ctx; 47 | }; 48 | 49 | zrender.util.$override('measureText', function (text, font) { 50 | ctx.font = font || '12px sans-serif'; 51 | return ctx.measureText(text); 52 | }); 53 | } 54 | 55 | _initStyle(ctx) { 56 | ctx.createRadialGradient = () => { 57 | return ctx.createCircularGradient(arguments); 58 | }; 59 | } 60 | 61 | _initEvent() { 62 | this.event = {}; 63 | const eventNames = [{ 64 | wxName: 'touchStart', 65 | ecName: 'mousedown' 66 | }, { 67 | wxName: 'touchMove', 68 | ecName: 'mousemove' 69 | }, { 70 | wxName: 'touchEnd', 71 | ecName: 'mouseup' 72 | }, { 73 | wxName: 'touchEnd', 74 | ecName: 'click' 75 | }]; 76 | 77 | eventNames.forEach(name => { 78 | this.event[name.wxName] = e => { 79 | const touch = e.touches[0]; 80 | this.chart.getZr().handler.dispatch(name.ecName, { 81 | zrX: name.wxName === 'tap' ? touch.clientX : touch.x, 82 | zrY: name.wxName === 'tap' ? touch.clientY : touch.y 83 | }); 84 | }; 85 | }); 86 | } 87 | 88 | set width(w) { 89 | if (this.canvasNode) this.canvasNode.width = w 90 | } 91 | set height(h) { 92 | if (this.canvasNode) this.canvasNode.height = h 93 | } 94 | 95 | get width() { 96 | if (this.canvasNode) 97 | return this.canvasNode.width 98 | return 0 99 | } 100 | get height() { 101 | if (this.canvasNode) 102 | return this.canvasNode.height 103 | return 0 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /mysql/init.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat Premium Data Transfer 3 | 4 | Source Server : 5 | Source Server Type : MySQL 6 | Source Server Version : 50731 7 | Source Host : 8 | Source Schema : easy-test 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 50731 12 | File Encoding : 65001 13 | 14 | Date: 17/07/2020 14:28:13 15 | */ 16 | 17 | SET NAMES utf8mb4; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for lin_user 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `lin_user`; 24 | CREATE TABLE `lin_user` ( 25 | `create_time` datetime(0) DEFAULT NULL, 26 | `update_time` datetime(0) DEFAULT NULL, 27 | `delete_time` datetime(0) DEFAULT NULL, 28 | `id` int(11) NOT NULL AUTO_INCREMENT, 29 | `username` varchar(24) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名', 30 | `nickname` varchar(24) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '昵称', 31 | `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '头像url', 32 | `admin` smallint(6) NOT NULL COMMENT '是否为超级管理员 ; 1 -> 普通用户 | 2 -> 超级管理员', 33 | `active` smallint(6) NOT NULL COMMENT '当前用户是否为激活状态,非激活状态默认失去用户权限 ; 1 -> 激活 | 2 -> 非激活', 34 | `email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '电子邮箱', 35 | `group_id` int(11) DEFAULT NULL COMMENT '用户所属的权限组id', 36 | `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '密码', 37 | `phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '手机号', 38 | `openid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '微信openid', 39 | PRIMARY KEY (`id`) USING BTREE, 40 | UNIQUE INDEX `username`(`username`) USING BTREE, 41 | UNIQUE INDEX `nickname`(`nickname`) USING BTREE, 42 | UNIQUE INDEX `email`(`email`) USING BTREE, 43 | UNIQUE INDEX `phone`(`phone`) USING BTREE, 44 | UNIQUE INDEX `openid`(`openid`) USING BTREE 45 | ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; 46 | 47 | -- ---------------------------- 48 | -- Records of lin_user 49 | -- ---------------------------- 50 | 51 | INSERT INTO `lin_user` VALUES ('2020-07-17 06:27:46', '2020-07-17 06:27:46', NULL, 1, 'super', NULL, NULL, 2, 1, '1234995678@qq.com', NULL, 'pbkdf2:sha256:50000$BUN0Mf0a$61294b144679014cdd7b40bf9a2233d987911273c9eb20935aa8eac4a14b66d4', NULL, NULL); 52 | INSERT INTO `lin_user` VALUES ('2020-04-06 09:47:23', '2020-04-06 10:05:55', NULL, 2, 'cron', NULL, NULL, 1, 2, NULL, NULL, NULL, NULL, NULL); 53 | 54 | SET FOREIGN_KEY_CHECKS = 1; 55 | -------------------------------------------------------------------------------- /easy-test-vue/src/views/book/BookAdd.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 新建图书 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 25 | 26 | 27 | 28 | 29 | 保 存 30 | 重 置 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 73 | 74 | 95 | --------------------------------------------------------------------------------
{{text}}
31 | 遇事不决 可问春风
{{ item }}