├── __init__.py ├── vige-api ├── .gitkeep ├── __init__.py ├── vige │ ├── __init__.py │ ├── api │ │ ├── bo_user │ │ │ ├── __init__.py │ │ │ ├── constants.py │ │ │ ├── factories.py │ │ │ ├── verify_code.py │ │ │ └── forms.py │ │ ├── media │ │ │ ├── __init__.py │ │ │ ├── forms.py │ │ │ └── api.py │ │ ├── settings │ │ │ ├── __init__.py │ │ │ ├── models.py │ │ │ ├── forms.py │ │ │ ├── settings.py │ │ │ ├── api.py │ │ │ └── base.py │ │ ├── users │ │ │ ├── __init__.py │ │ │ ├── factories.py │ │ │ └── forms.py │ │ ├── notifications │ │ │ ├── __init__.py │ │ │ ├── templates.py │ │ │ └── tasks.py │ │ ├── wechat │ │ │ ├── __init__.py │ │ │ ├── form.py │ │ │ ├── wechat_pay_base.py │ │ │ └── tasks.py │ │ ├── forms.py │ │ ├── fixtures.py │ │ ├── __init__.py │ │ ├── errors.py │ │ ├── decorators.py │ │ └── constants.py │ ├── migrations │ │ ├── __init__.py │ │ ├── versions │ │ │ ├── __init__.py │ │ │ ├── 5242cbfe15aa_disable_role.py │ │ │ ├── 26b56fd7a0cd_settings_model.py │ │ │ ├── 1804876c9b60_bind_wechat.py │ │ │ └── 16e4955fffad_media_model.py │ │ ├── README │ │ ├── script.py.mako │ │ └── env.py │ ├── tests │ │ ├── test_ping.py │ │ ├── test_i18n.py │ │ └── test_db.py │ ├── local_config.env │ ├── translations │ │ ├── messages.cfg │ │ └── zh │ │ │ └── LC_MESSAGES │ │ │ └── messages.po │ ├── app.py │ ├── huey_app.py │ ├── huey_config.py │ ├── i18n.py │ ├── log.py │ └── test_utils.py ├── .env ├── pytest.ini ├── .dockerignore ├── .coveragerc ├── run_sio.py ├── tox.ini ├── entrypoint.sh ├── Dockerfile ├── setup.py ├── alembic.ini ├── Pipfile └── Makefile ├── vige-web ├── .eslintignore ├── config │ ├── env.js │ └── url.js ├── .browserslistrc ├── cypress.json ├── src │ ├── views │ │ └── main │ │ │ ├── index.js │ │ │ ├── components │ │ │ ├── user │ │ │ │ ├── index.js │ │ │ │ ├── user.less │ │ │ │ └── user.vue │ │ │ ├── language │ │ │ │ ├── index.js │ │ │ │ └── language.vue │ │ │ ├── header-bar │ │ │ │ ├── index.js │ │ │ │ ├── sider-trigger │ │ │ │ │ ├── index.js │ │ │ │ │ ├── sider-trigger.less │ │ │ │ │ └── sider-trigger.vue │ │ │ │ ├── custom-bread-crumb │ │ │ │ │ ├── index.js │ │ │ │ │ ├── custom-bread-crumb.less │ │ │ │ │ └── custom-bread-crumb.vue │ │ │ │ ├── header-bar.less │ │ │ │ └── header-bar.vue │ │ │ └── fullscreen │ │ │ │ └── index.js │ │ │ └── main.less │ ├── components │ │ ├── icons │ │ │ ├── index.js │ │ │ └── icons.vue │ │ ├── common-icon │ │ │ ├── index.js │ │ │ └── common-icon.vue │ │ ├── common │ │ │ ├── util.js │ │ │ └── common.less │ │ └── preview │ │ │ └── image-preview.vue │ ├── assets │ │ ├── logo.png │ │ ├── images │ │ │ ├── add.png │ │ │ ├── logo.png │ │ │ ├── new.png │ │ │ ├── pad.png │ │ │ ├── goback.png │ │ │ ├── loading.png │ │ │ ├── mobile.png │ │ │ ├── td-logo.png │ │ │ ├── deepseek.png │ │ │ ├── login-bg.png │ │ │ ├── logo-min.png │ │ │ ├── td-avatar.jpeg │ │ │ ├── home-banner.png │ │ │ ├── image-upload.png │ │ │ ├── modal-close.png │ │ │ ├── upload-delete.png │ │ │ └── upload-replace.png │ │ └── icons │ │ │ ├── iconfont.eot │ │ │ ├── iconfont.ttf │ │ │ └── iconfont.woff │ ├── config │ │ ├── config.js │ │ └── index.js │ ├── libs │ │ ├── constants.js │ │ └── filters.js │ ├── styles │ │ └── theme.less │ ├── index.less │ ├── locale │ │ ├── lang │ │ │ ├── zh-CN.js │ │ │ ├── zh-TW.js │ │ │ └── en-US.js │ │ └── index.js │ ├── router │ │ ├── routers.js │ │ └── index.js │ ├── main.js │ ├── store.js │ └── App.vue ├── babel.config.js ├── .postcssrc.js ├── public │ ├── favicon.png │ └── index.html ├── .gitignore ├── .eslintrc.js ├── README.md └── package.json ├── vige-wechat ├── .eslintignore ├── config │ ├── env.js │ └── url.js ├── .browserslistrc ├── cypress.json ├── babel.config.js ├── .postcssrc.js ├── public │ ├── favicon.png │ └── index.html ├── src │ ├── assets │ │ └── logo.png │ ├── plugin │ │ └── wechat │ │ │ └── index.js │ ├── views │ │ ├── Home.vue │ │ ├── auth.vue │ │ ├── bo-workbench.vue │ │ └── bind-bo-user.vue │ ├── main.js │ ├── store.js │ ├── router.js │ ├── libs │ │ └── api.js │ └── components │ │ └── HelloWorld.vue ├── tests │ ├── unit │ │ ├── .eslintrc.js │ │ └── HelloWorld.spec.js │ └── e2e │ │ ├── .eslintrc.js │ │ ├── specs │ │ └── test.js │ │ ├── plugins │ │ └── index.js │ │ └── support │ │ ├── index.js │ │ └── commands.js ├── .gitignore ├── .eslintrc.js ├── README.md ├── package.json └── vue.config.js ├── vige-bo ├── config │ ├── env.js │ └── url.js ├── .babelrc ├── .eslintignore ├── cypress.json ├── src │ ├── view │ │ ├── main │ │ │ ├── index.js │ │ │ ├── components │ │ │ │ ├── user │ │ │ │ │ ├── index.js │ │ │ │ │ └── user.less │ │ │ │ ├── header-bar │ │ │ │ │ ├── index.js │ │ │ │ │ ├── sider-trigger │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ ├── sider-trigger.less │ │ │ │ │ │ └── sider-trigger.vue │ │ │ │ │ ├── custom-bread-crumb │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ ├── custom-bread-crumb.less │ │ │ │ │ │ └── custom-bread-crumb.vue │ │ │ │ │ ├── header-bar.less │ │ │ │ │ └── header-bar.vue │ │ │ │ ├── language │ │ │ │ │ ├── index.js │ │ │ │ │ └── language.vue │ │ │ │ ├── side-menu │ │ │ │ │ ├── index.js │ │ │ │ │ ├── item-mixin.js │ │ │ │ │ ├── mixin.js │ │ │ │ │ ├── side-menu.less │ │ │ │ │ ├── side-menu-item.vue │ │ │ │ │ └── collapsed-menu.vue │ │ │ │ └── fullscreen │ │ │ │ │ └── index.js │ │ │ └── main.less │ │ ├── single-page │ │ │ └── home │ │ │ │ ├── index.js │ │ │ │ └── home.vue │ │ ├── error-page │ │ │ ├── 404.vue │ │ │ ├── 500.vue │ │ │ ├── 401.vue │ │ │ ├── 403.vue │ │ │ ├── error-content.vue │ │ │ ├── back-btn-group.vue │ │ │ └── error.less │ │ └── login │ │ │ ├── login.less │ │ │ └── login.vue │ ├── components │ │ ├── icons │ │ │ ├── index.js │ │ │ └── icons.vue │ │ ├── split-pane │ │ │ ├── index.js │ │ │ └── trigger.vue │ │ ├── count-to │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── tables │ │ │ ├── index.js │ │ │ ├── index.less │ │ │ ├── extend-table-col.vue │ │ │ ├── invalid-col.vue │ │ │ ├── poptip-btn.vue │ │ │ ├── actions-col.vue │ │ │ ├── table-importer-mixin.js │ │ │ ├── confirm-button.vue │ │ │ ├── qr-code-col.vue │ │ │ └── search-input.vue │ │ ├── info-card │ │ │ ├── index.js │ │ │ └── infor-card.vue │ │ ├── login-form │ │ │ └── index.js │ │ ├── common-icon │ │ │ ├── index.js │ │ │ └── common-icon.vue │ │ ├── markdown │ │ │ ├── index.js │ │ │ └── markdown.vue │ │ ├── parent-view │ │ │ ├── index.js │ │ │ └── parent-view.vue │ │ ├── charts │ │ │ ├── index.js │ │ │ ├── bar.vue │ │ │ └── pie.vue │ │ ├── common │ │ │ ├── util.js │ │ │ └── common.less │ │ └── Tinymce │ │ │ ├── toolbar.js │ │ │ ├── plugins.js │ │ │ └── dynamicLoadScript.js │ ├── libs │ │ ├── constants.js │ │ ├── filters.js │ │ └── api.js │ ├── assets │ │ ├── images │ │ │ ├── logo.png │ │ │ ├── login-bg.jpg │ │ │ ├── logo-min.png │ │ │ ├── image-upload.png │ │ │ ├── upload-delete.png │ │ │ └── upload-replace.png │ │ └── icons │ │ │ ├── iconfont.eot │ │ │ ├── iconfont.ttf │ │ │ └── iconfont.woff │ ├── styles │ │ ├── theme.less │ │ └── common.less │ ├── index.less │ ├── config │ │ └── index.js │ ├── store │ │ ├── index.js │ │ └── module │ │ │ └── app.js │ ├── locale │ │ ├── lang │ │ │ ├── zh-CN.js │ │ │ ├── zh-TW.js │ │ │ └── en-US.js │ │ └── index.js │ ├── mixins │ │ └── formErrorMixin.js │ ├── app.vue │ └── router │ │ └── index.js ├── public │ ├── favicon.png │ └── index.html ├── .gitignore ├── tests │ ├── unit │ │ ├── .eslintrc.js │ │ └── HelloWorld.spec.js │ └── e2e │ │ ├── .eslintrc │ │ ├── specs │ │ └── test.js │ │ ├── plugins │ │ └── index.js │ │ └── support │ │ ├── index.js │ │ └── commands.js ├── .editorconfig ├── README.md ├── .eslintrc.json ├── LICENSE ├── vue.config.js └── package.json ├── SECURITY.md ├── CODE_OF_CONDUCT.md ├── .github ├── pull_request_template.md ├── ISSUE_TEMPLATE │ ├── feature.yml │ └── bug.yml ├── dependabot.yml └── workflows │ ├── frontend.yml │ └── backend.yml ├── .editorconfig ├── .gitignore ├── CONTRIBUTING.md ├── .pre-commit-config.yaml ├── docker-compose.yml ├── LICENSE └── Makefile /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vige-api/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vige-api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vige-api/vige/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vige-api/vige/api/bo_user/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vige-api/vige/api/bo_user/constants.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vige-api/vige/api/media/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vige-api/vige/api/settings/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vige-api/vige/api/users/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vige-api/vige/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vige-api/vige/api/notifications/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vige-web/.eslintignore: -------------------------------------------------------------------------------- 1 | src/plugin/wechat/* -------------------------------------------------------------------------------- /vige-api/vige/migrations/versions/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vige-wechat/.eslintignore: -------------------------------------------------------------------------------- 1 | src/plugin/wechat/* -------------------------------------------------------------------------------- /vige-api/.env: -------------------------------------------------------------------------------- 1 | PYTHONPATH=/Users/Versus/vige/vige-api 2 | -------------------------------------------------------------------------------- /vige-api/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = -lx --ff 3 | -------------------------------------------------------------------------------- /vige-bo/config/env.js: -------------------------------------------------------------------------------- 1 | export default 'development' 2 | -------------------------------------------------------------------------------- /vige-web/config/env.js: -------------------------------------------------------------------------------- 1 | export default 'development' 2 | -------------------------------------------------------------------------------- /vige-wechat/config/env.js: -------------------------------------------------------------------------------- 1 | export default 'development' 2 | -------------------------------------------------------------------------------- /vige-api/vige/api/wechat/__init__.py: -------------------------------------------------------------------------------- 1 | from .wechat import wechat -------------------------------------------------------------------------------- /vige-api/vige/migrations/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /vige-bo/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@vue/app" 4 | ] 5 | } -------------------------------------------------------------------------------- /vige-web/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /vige-wechat/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /vige-bo/.eslintignore: -------------------------------------------------------------------------------- 1 | src/router.js 2 | src/libs/util.js 3 | src/vendors.js -------------------------------------------------------------------------------- /vige-bo/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "tests/e2e/plugins/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /vige-web/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "tests/e2e/plugins/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /vige-bo/src/view/main/index.js: -------------------------------------------------------------------------------- 1 | import Main from './main.vue' 2 | export default Main 3 | -------------------------------------------------------------------------------- /vige-wechat/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "tests/e2e/plugins/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /vige-web/src/views/main/index.js: -------------------------------------------------------------------------------- 1 | import Main from './main.vue' 2 | export default Main 3 | -------------------------------------------------------------------------------- /vige-bo/src/components/icons/index.js: -------------------------------------------------------------------------------- 1 | import Icons from './icons.vue' 2 | export default Icons 3 | -------------------------------------------------------------------------------- /vige-bo/src/libs/constants.js: -------------------------------------------------------------------------------- 1 | 2 | export const APP_NAME = 'vige' 3 | export const BO_NAME = 'VIGE' -------------------------------------------------------------------------------- /vige-web/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /vige-bo/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-bo/public/favicon.png -------------------------------------------------------------------------------- /vige-bo/src/components/split-pane/index.js: -------------------------------------------------------------------------------- 1 | import Split from './split.vue' 2 | export default Split 3 | -------------------------------------------------------------------------------- /vige-bo/src/view/single-page/home/index.js: -------------------------------------------------------------------------------- 1 | import home from './home.vue' 2 | export default home 3 | -------------------------------------------------------------------------------- /vige-web/.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /vige-web/src/components/icons/index.js: -------------------------------------------------------------------------------- 1 | import Icons from './icons.vue' 2 | export default Icons 3 | -------------------------------------------------------------------------------- /vige-wechat/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /vige-bo/src/components/count-to/index.js: -------------------------------------------------------------------------------- 1 | import countTo from './count-to.vue' 2 | export default countTo 3 | -------------------------------------------------------------------------------- /vige-bo/src/view/main/components/user/index.js: -------------------------------------------------------------------------------- 1 | import User from './user.vue' 2 | export default User 3 | -------------------------------------------------------------------------------- /vige-web/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/public/favicon.png -------------------------------------------------------------------------------- /vige-web/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/src/assets/logo.png -------------------------------------------------------------------------------- /vige-web/src/config/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | debug: true 3 | } 4 | 5 | export default config 6 | -------------------------------------------------------------------------------- /vige-web/src/views/main/components/user/index.js: -------------------------------------------------------------------------------- 1 | import User from './user.vue' 2 | export default User 3 | -------------------------------------------------------------------------------- /vige-wechat/.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /vige-bo/src/components/tables/index.js: -------------------------------------------------------------------------------- 1 | import BaseTable from './base-table.vue' 2 | export default BaseTable 3 | -------------------------------------------------------------------------------- /vige-web/src/libs/constants.js: -------------------------------------------------------------------------------- 1 | 2 | export const APP_NAME = 'dpai' 3 | export const BO_NAME = 'Your platform name' -------------------------------------------------------------------------------- /vige-wechat/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-wechat/public/favicon.png -------------------------------------------------------------------------------- /vige-wechat/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-wechat/src/assets/logo.png -------------------------------------------------------------------------------- /vige-bo/src/components/info-card/index.js: -------------------------------------------------------------------------------- 1 | import InforCard from './infor-card.vue' 2 | export default InforCard 3 | -------------------------------------------------------------------------------- /vige-bo/src/components/login-form/index.js: -------------------------------------------------------------------------------- 1 | import LoginForm from './login-form.vue' 2 | export default LoginForm 3 | -------------------------------------------------------------------------------- /vige-bo/src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-bo/src/assets/images/logo.png -------------------------------------------------------------------------------- /vige-bo/src/components/common-icon/index.js: -------------------------------------------------------------------------------- 1 | import CommonIcon from './common-icon.vue' 2 | export default CommonIcon 3 | -------------------------------------------------------------------------------- /vige-bo/src/components/markdown/index.js: -------------------------------------------------------------------------------- 1 | import MarkdownEditor from './markdown.vue' 2 | export default MarkdownEditor 3 | -------------------------------------------------------------------------------- /vige-bo/src/components/parent-view/index.js: -------------------------------------------------------------------------------- 1 | import ParentView from './parent-view.vue' 2 | export default ParentView 3 | -------------------------------------------------------------------------------- /vige-bo/src/view/main/components/header-bar/index.js: -------------------------------------------------------------------------------- 1 | import HeaderBar from './header-bar' 2 | export default HeaderBar 3 | -------------------------------------------------------------------------------- /vige-bo/src/view/main/components/language/index.js: -------------------------------------------------------------------------------- 1 | import Language from './language.vue' 2 | export default Language 3 | -------------------------------------------------------------------------------- /vige-bo/src/view/main/components/side-menu/index.js: -------------------------------------------------------------------------------- 1 | import SideMenu from './side-menu.vue' 2 | export default SideMenu 3 | -------------------------------------------------------------------------------- /vige-web/src/assets/images/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/src/assets/images/add.png -------------------------------------------------------------------------------- /vige-web/src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/src/assets/images/logo.png -------------------------------------------------------------------------------- /vige-web/src/assets/images/new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/src/assets/images/new.png -------------------------------------------------------------------------------- /vige-web/src/assets/images/pad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/src/assets/images/pad.png -------------------------------------------------------------------------------- /vige-web/src/components/common-icon/index.js: -------------------------------------------------------------------------------- 1 | import CommonIcon from './common-icon.vue' 2 | export default CommonIcon 3 | -------------------------------------------------------------------------------- /vige-web/src/views/main/components/language/index.js: -------------------------------------------------------------------------------- 1 | import Language from './language.vue' 2 | export default Language 3 | -------------------------------------------------------------------------------- /vige-bo/src/assets/icons/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-bo/src/assets/icons/iconfont.eot -------------------------------------------------------------------------------- /vige-bo/src/assets/icons/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-bo/src/assets/icons/iconfont.ttf -------------------------------------------------------------------------------- /vige-bo/src/assets/icons/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-bo/src/assets/icons/iconfont.woff -------------------------------------------------------------------------------- /vige-bo/src/assets/images/login-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-bo/src/assets/images/login-bg.jpg -------------------------------------------------------------------------------- /vige-bo/src/assets/images/logo-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-bo/src/assets/images/logo-min.png -------------------------------------------------------------------------------- /vige-bo/src/view/main/components/fullscreen/index.js: -------------------------------------------------------------------------------- 1 | import Fullscreen from './fullscreen.vue' 2 | export default Fullscreen 3 | -------------------------------------------------------------------------------- /vige-web/src/assets/icons/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/src/assets/icons/iconfont.eot -------------------------------------------------------------------------------- /vige-web/src/assets/icons/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/src/assets/icons/iconfont.ttf -------------------------------------------------------------------------------- /vige-web/src/assets/images/goback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/src/assets/images/goback.png -------------------------------------------------------------------------------- /vige-web/src/assets/images/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/src/assets/images/loading.png -------------------------------------------------------------------------------- /vige-web/src/assets/images/mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/src/assets/images/mobile.png -------------------------------------------------------------------------------- /vige-web/src/assets/images/td-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/src/assets/images/td-logo.png -------------------------------------------------------------------------------- /vige-web/src/views/main/components/header-bar/index.js: -------------------------------------------------------------------------------- 1 | import HeaderBar from './header-bar' 2 | export default HeaderBar 3 | -------------------------------------------------------------------------------- /vige-web/src/assets/icons/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/src/assets/icons/iconfont.woff -------------------------------------------------------------------------------- /vige-web/src/assets/images/deepseek.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/src/assets/images/deepseek.png -------------------------------------------------------------------------------- /vige-web/src/assets/images/login-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/src/assets/images/login-bg.png -------------------------------------------------------------------------------- /vige-web/src/assets/images/logo-min.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/src/assets/images/logo-min.png -------------------------------------------------------------------------------- /vige-web/src/assets/images/td-avatar.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/src/assets/images/td-avatar.jpeg -------------------------------------------------------------------------------- /vige-web/src/views/main/components/fullscreen/index.js: -------------------------------------------------------------------------------- 1 | import Fullscreen from './fullscreen.vue' 2 | export default Fullscreen 3 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## 安全策略 2 | 3 | 如发现安全漏洞,请不要在 Issue 中公开披露。 4 | 5 | 请发送邮件至:versus132017@gmail.com。 6 | 我们会在 72 小时内响应,并在修复后发布安全公告。 7 | 8 | -------------------------------------------------------------------------------- /vige-bo/src/assets/images/image-upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-bo/src/assets/images/image-upload.png -------------------------------------------------------------------------------- /vige-bo/src/assets/images/upload-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-bo/src/assets/images/upload-delete.png -------------------------------------------------------------------------------- /vige-web/src/assets/images/home-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/src/assets/images/home-banner.png -------------------------------------------------------------------------------- /vige-web/src/assets/images/image-upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/src/assets/images/image-upload.png -------------------------------------------------------------------------------- /vige-web/src/assets/images/modal-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/src/assets/images/modal-close.png -------------------------------------------------------------------------------- /vige-bo/src/assets/images/upload-replace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-bo/src/assets/images/upload-replace.png -------------------------------------------------------------------------------- /vige-web/src/assets/images/upload-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/src/assets/images/upload-delete.png -------------------------------------------------------------------------------- /vige-web/src/assets/images/upload-replace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Versus2017/vige/HEAD/vige-web/src/assets/images/upload-replace.png -------------------------------------------------------------------------------- /vige-bo/src/components/charts/index.js: -------------------------------------------------------------------------------- 1 | import ChartPie from './pie.vue' 2 | import ChartBar from './bar.vue' 3 | export { ChartPie, ChartBar } 4 | -------------------------------------------------------------------------------- /vige-bo/src/view/main/components/header-bar/sider-trigger/index.js: -------------------------------------------------------------------------------- 1 | import siderTrigger from './sider-trigger.vue' 2 | export default siderTrigger 3 | -------------------------------------------------------------------------------- /vige-web/src/views/main/components/header-bar/sider-trigger/index.js: -------------------------------------------------------------------------------- 1 | import siderTrigger from './sider-trigger.vue' 2 | export default siderTrigger 3 | -------------------------------------------------------------------------------- /vige-bo/src/styles/theme.less: -------------------------------------------------------------------------------- 1 | @import '~iview/src/styles/index.less'; 2 | 3 | @warning-color : orange; 4 | @success-color : lightseagreen; 5 | -------------------------------------------------------------------------------- /vige-web/src/styles/theme.less: -------------------------------------------------------------------------------- 1 | @import '~iview/src/styles/index.less'; 2 | 3 | @warning-color : orange; 4 | @success-color : lightseagreen; 5 | -------------------------------------------------------------------------------- /vige-bo/src/view/main/components/header-bar/custom-bread-crumb/index.js: -------------------------------------------------------------------------------- 1 | import customBreadCrumb from './custom-bread-crumb.vue' 2 | export default customBreadCrumb 3 | -------------------------------------------------------------------------------- /vige-bo/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .idea/ 3 | .DS_Store 4 | node_modules/ 5 | .project 6 | dist 7 | dist/* 8 | src/config/*.tmp 9 | src/config/env.js 10 | npm-debug.log -------------------------------------------------------------------------------- /vige-bo/src/view/main/components/header-bar/custom-bread-crumb/custom-bread-crumb.less: -------------------------------------------------------------------------------- 1 | .custom-bread-crumb{ 2 | display: inline-block; 3 | vertical-align: top; 4 | } 5 | -------------------------------------------------------------------------------- /vige-bo/tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true 4 | }, 5 | rules: { 6 | 'import/no-extraneous-dependencies': 'off' 7 | } 8 | } -------------------------------------------------------------------------------- /vige-web/src/views/main/components/header-bar/custom-bread-crumb/index.js: -------------------------------------------------------------------------------- 1 | import customBreadCrumb from './custom-bread-crumb.vue' 2 | export default customBreadCrumb 3 | -------------------------------------------------------------------------------- /vige-bo/src/index.less: -------------------------------------------------------------------------------- 1 | @import '~iview/src/styles/index.less'; 2 | 3 | @menu-dark-title: #001529; 4 | @menu-dark-active-bg: #000c17; 5 | @layout-sider-background: #001529; 6 | -------------------------------------------------------------------------------- /vige-web/src/index.less: -------------------------------------------------------------------------------- 1 | @import '~iview/src/styles/index.less'; 2 | 3 | @menu-dark-title: #001529; 4 | @menu-dark-active-bg: #000c17; 5 | @layout-sider-background: #001529; 6 | -------------------------------------------------------------------------------- /vige-web/src/views/main/components/header-bar/custom-bread-crumb/custom-bread-crumb.less: -------------------------------------------------------------------------------- 1 | .custom-bread-crumb{ 2 | display: inline-block; 3 | vertical-align: top; 4 | } 5 | -------------------------------------------------------------------------------- /vige-wechat/tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true 4 | }, 5 | rules: { 6 | 'import/no-extraneous-dependencies': 'off' 7 | } 8 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## 行为准则 2 | 3 | 为营造友好、包容的社区环境,参与者应: 4 | - 尊重他人,不进行人身攻击或歧视; 5 | - 接受建设性意见; 6 | - 遵守项目维护者的决定与协作流程。 7 | 8 | 如有违规或安全问题,请通过 SECURITY.md 中的联系方式私下报告。 9 | 10 | -------------------------------------------------------------------------------- /vige-bo/src/components/parent-view/parent-view.vue: -------------------------------------------------------------------------------- 1 | 4 | 9 | -------------------------------------------------------------------------------- /vige-bo/src/components/common/util.js: -------------------------------------------------------------------------------- 1 | export const showTitle = (item, vm) => { 2 | return vm.$config.useI18n ? vm.$t(item.name) : ((item.meta && item.meta.title) || item.name) 3 | } 4 | -------------------------------------------------------------------------------- /vige-web/src/components/common/util.js: -------------------------------------------------------------------------------- 1 | export const showTitle = (item, vm) => { 2 | return vm.$config.useI18n ? vm.$t(item.name) : ((item.meta && item.meta.title) || item.name) 3 | } 4 | -------------------------------------------------------------------------------- /vige-api/vige/tests/test_ping.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def test_ping(client): 4 | resp = client.get('/v1/ping') 5 | assert resp.status_code == 200 6 | assert 'success' in resp.json['data'] 7 | -------------------------------------------------------------------------------- /vige-bo/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | charset = utf-8 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /vige-api/vige/local_config.env: -------------------------------------------------------------------------------- 1 | # You may want to change this 2 | GPT_TOKEN='jwt_secret_key' 3 | GPT_PROXY='http://127.0.0.1:7890' 4 | AUTHJWT_SECRET_KEY='secret_key' 5 | ZHIPU_API_KEY='api_key' 6 | -------------------------------------------------------------------------------- /vige-bo/tests/e2e/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "cypress" 4 | ], 5 | "env": { 6 | "mocha": true, 7 | "cypress/globals": true 8 | }, 9 | "rules": { 10 | "strict": "off" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /vige-bo/README.md: -------------------------------------------------------------------------------- 1 | ```angular2html 2 | # install dependencies 3 | yarn install 4 | yarn global add @vue/cli-service-global 5 | 6 | # run lint 7 | yarn run lint 8 | 9 | # run in dev mode 10 | yarn run dev 11 | 12 | ``` 13 | -------------------------------------------------------------------------------- /vige-bo/src/config/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | /** 3 | * @description 是否使用国际化,默认为false 4 | * 如果不使用,则需要在路由中给需要在菜单中展示的路由设置meta: {title: 'xxx'} 5 | * 用来在菜单中显示文字 6 | */ 7 | useI18n: false 8 | } 9 | -------------------------------------------------------------------------------- /vige-web/src/config/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | /** 3 | * @description 是否使用国际化,默认为false 4 | * 如果不使用,则需要在路由中给需要在菜单中展示的路由设置meta: {title: 'xxx'} 5 | * 用来在菜单中显示文字 6 | */ 7 | useI18n: false 8 | } 9 | -------------------------------------------------------------------------------- /vige-web/src/views/main/components/header-bar/header-bar.less: -------------------------------------------------------------------------------- 1 | .header-bar{ 2 | width: 100%; 3 | height: 100%; 4 | //background-color: red; 5 | display: flex; 6 | align-items: center; 7 | justify-content: space-between; 8 | } 9 | -------------------------------------------------------------------------------- /vige-wechat/tests/e2e/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | 'cypress' 4 | ], 5 | env: { 6 | mocha: true, 7 | 'cypress/globals': true 8 | }, 9 | rules: { 10 | strict: 'off' 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### 变更内容 2 | - [ ] 功能新增 3 | - [ ] Bug 修复 4 | - [ ] 文档更新 5 | - [ ] 构建/CI 6 | 7 | ### 说明 8 | 简述本 PR 做了什么、为什么需要。 9 | 10 | ### 清单 11 | - [ ] 遵循项目代码规范 12 | - [ ] 本地通过构建/测试 13 | - [ ] 如有破坏性改动,已在说明中标注 14 | 15 | -------------------------------------------------------------------------------- /vige-api/vige/api/forms.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from pydantic import BaseModel 3 | 4 | 5 | class BaseFilterForm(BaseModel): 6 | keyword: Optional[str] = None 7 | page: Optional[int] = 1 8 | per_page: Optional[int] = 10 9 | -------------------------------------------------------------------------------- /vige-bo/src/components/common/common.less: -------------------------------------------------------------------------------- 1 | .no-select{ 2 | -webkit-touch-callout: none; 3 | -webkit-user-select: none; 4 | -khtml-user-select: none; 5 | -moz-user-select: none; 6 | -ms-user-select: none; 7 | user-select: none; 8 | } 9 | -------------------------------------------------------------------------------- /vige-bo/src/components/count-to/index.less: -------------------------------------------------------------------------------- 1 | @prefix: ~"count-to"; 2 | 3 | .@{prefix}-wrapper{ 4 | .content-outer{ 5 | display: inline-block; 6 | .@{prefix}-unit-text{ 7 | font-style: normal; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /vige-web/src/components/common/common.less: -------------------------------------------------------------------------------- 1 | .no-select{ 2 | -webkit-touch-callout: none; 3 | -webkit-user-select: none; 4 | -khtml-user-select: none; 5 | -moz-user-select: none; 6 | -ms-user-select: none; 7 | user-select: none; 8 | } 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.py] 12 | indent_size = 4 13 | 14 | -------------------------------------------------------------------------------- /vige-api/vige/translations/messages.cfg: -------------------------------------------------------------------------------- 1 | [ignore: **/**.sw*] 2 | [ignore: **/tests/**.py] 3 | [ignore: **/migrations/**.py] 4 | 5 | [python: **.py] 6 | [jinja2: **/templates/**] 7 | extensions=jinja2.ext.autoescape,jinja2.ext.with_,jinja2.ext.i18n 8 | -------------------------------------------------------------------------------- /vige-bo/config/url.js: -------------------------------------------------------------------------------- 1 | import env from './env' 2 | 3 | const DEV_URL = 'https://www.easy-mock.com/mock/5add9213ce4d0e69998a6f51/iview-admin/' 4 | const PRO_URL = 'https://produce.com' 5 | 6 | export default env === 'development' ? DEV_URL : PRO_URL 7 | -------------------------------------------------------------------------------- /vige-bo/src/view/main/components/user/user.less: -------------------------------------------------------------------------------- 1 | .user{ 2 | &-avator-dropdown{ 3 | cursor: pointer; 4 | display: inline-block; 5 | // height: 64px; 6 | vertical-align: middle; 7 | // line-height: 64px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /vige-web/src/views/main/components/user/user.less: -------------------------------------------------------------------------------- 1 | .user{ 2 | &-avator-dropdown{ 3 | cursor: pointer; 4 | display: inline-block; 5 | // height: 64px; 6 | vertical-align: middle; 7 | // line-height: 64px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /vige-web/config/url.js: -------------------------------------------------------------------------------- 1 | import env from './env' 2 | 3 | const DEV_URL = 'https://www.easy-mock.com/mock/5add9213ce4d0e69998a6f51/iview-admin/' 4 | const PRO_URL = 'https://produce.com' 5 | 6 | export default env === 'development' ? DEV_URL : PRO_URL 7 | -------------------------------------------------------------------------------- /vige-wechat/config/url.js: -------------------------------------------------------------------------------- 1 | import env from './env' 2 | 3 | const DEV_URL = 'https://www.easy-mock.com/mock/5add9213ce4d0e69998a6f51/iview-admin/' 4 | const PRO_URL = 'https://produce.com' 5 | 6 | export default env === 'development' ? DEV_URL : PRO_URL 7 | -------------------------------------------------------------------------------- /vige-bo/tests/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/api/introduction/api.html 2 | 3 | describe('My First Test', () => { 4 | it('Visits the app root url', () => { 5 | cy.visit('/') 6 | cy.contains('h1', 'Welcome to Your Vue.js App') 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /vige-wechat/tests/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/api/introduction/api.html 2 | 3 | describe('My First Test', () => { 4 | it('Visits the app root url', () => { 5 | cy.visit('/') 6 | cy.contains('h1', 'Welcome to Your Vue.js App') 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /vige-api/vige/api/notifications/templates.py: -------------------------------------------------------------------------------- 1 | 2 | class SMS_TEMPLATE: 3 | # 教练登录模板 4 | LOGIN_VERIFICATION = 'SMS_296795040' 5 | # # 支付成功模板 6 | # ORDER_PAY_SUCCESS = 'SMS_218549556' 7 | # # 预约练车提前提醒模板 8 | # APPOINTMENT_IN_ADVANCE_NOTICE = 'SMS_218734384' 9 | -------------------------------------------------------------------------------- /vige-api/.dockerignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .pytest_cache 3 | .venv 4 | venv 5 | *.pyc 6 | *.pyo 7 | *.pyd 8 | *.log 9 | .git 10 | .gitignore 11 | local_config.env 12 | vige/local_config.env 13 | tests 14 | vige/tests 15 | dist 16 | build 17 | node_modules 18 | 19 | .venv 20 | .env -------------------------------------------------------------------------------- /vige-api/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = 3 | vige 4 | tests 5 | branch = True 6 | omit = 7 | .venv/* 8 | vige/cli.py 9 | 10 | [report] 11 | exclude_lines = 12 | no cov 13 | no qa 14 | noqa 15 | pragma: no cover 16 | if __name__ == .__main__.: 17 | -------------------------------------------------------------------------------- /vige-api/vige/app.py: -------------------------------------------------------------------------------- 1 | # !!! NEVER IMPORT THIS !!! 2 | # Only used for WSGI entrance 3 | # 4 | # * To create an `app`, use `app_factory` 5 | # * To refer to an `app`, use `flask.current_app` 6 | 7 | from .app_factory import get_full_app 8 | 9 | app = application = get_full_app() 10 | -------------------------------------------------------------------------------- /vige-api/run_sio.py: -------------------------------------------------------------------------------- 1 | from eventlet import monkey_patch 2 | monkey_patch() 3 | from vige.app_factory import get_full_app # noqa 4 | 5 | app = get_full_app() 6 | app.extensions['socketio'].run( 7 | app, host='0.0.0.0', 8 | use_reloader=app.debug, 9 | debug=app.debug, 10 | ) 11 | -------------------------------------------------------------------------------- /vige-wechat/src/plugin/wechat/index.js: -------------------------------------------------------------------------------- 1 | const wx = require('./wechat-plugin').wx 2 | 3 | const plugin = { 4 | install (Vue) { 5 | Vue.prototype.$wechat = wx 6 | Vue.wechat = wx 7 | }, 8 | $wechat: wx 9 | } 10 | 11 | export default plugin 12 | export const install = plugin.install -------------------------------------------------------------------------------- /vige-api/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py36, 4 | 5 | [testenv] 6 | passenv = * 7 | deps = 8 | coverage 9 | pytest 10 | commands = 11 | python setup.py --quiet clean develop 12 | coverage run --parallel-mode -m pytest 13 | coverage combine --append 14 | coverage report -m 15 | -------------------------------------------------------------------------------- /vige-bo/src/view/main/components/header-bar/header-bar.less: -------------------------------------------------------------------------------- 1 | .header-bar{ 2 | width: 100%; 3 | height: 100%; 4 | position: relative; 5 | .custom-content-con{ 6 | float: right; 7 | height: auto; 8 | padding-right: 20px; 9 | line-height: 64px; 10 | & > *{ 11 | float: right; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /vige-api/vige/tests/test_i18n.py: -------------------------------------------------------------------------------- 1 | from flask_babelex import get_locale 2 | 3 | 4 | def test_locale_selector(app): 5 | lang_header = { 6 | 'accept-language': 'zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4' 7 | } 8 | with app.test_request_context('/v1/ping/', headers=lang_header): 9 | assert get_locale().language == 'zh' 10 | -------------------------------------------------------------------------------- /vige-bo/src/components/tables/index.less: -------------------------------------------------------------------------------- 1 | .search-con{ 2 | padding: 10px 0; 3 | .search{ 4 | &-col{ 5 | display: inline-block; 6 | width: 200px; 7 | } 8 | &-input{ 9 | display: inline-block; 10 | width: 200px; 11 | margin-left: 2px; 12 | } 13 | &-btn{ 14 | margin-left: 2px; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yml: -------------------------------------------------------------------------------- 1 | name: 功能需求 2 | description: 提出新的功能或改进建议 3 | labels: [enhancement] 4 | body: 5 | - type: textarea 6 | id: desc 7 | attributes: 8 | label: 需求描述 9 | description: 你希望实现什么,为什么 10 | validations: 11 | required: true 12 | - type: textarea 13 | id: detail 14 | attributes: 15 | label: 详细方案/补充 16 | 17 | -------------------------------------------------------------------------------- /vige-api/vige/huey_app.py: -------------------------------------------------------------------------------- 1 | """ 2 | Huey entrypoint. 3 | 4 | import all tasks in this file. 5 | 6 | Run the consumer: `huey_consumer vige.huey_app.huey` 7 | """ 8 | from vige.huey_config import huey, logger # noqa 9 | # import all huey tasks here 10 | from vige.api.wechat import tasks 11 | 12 | @huey.task() 13 | def echo(what): 14 | logger.info(what) 15 | return what 16 | -------------------------------------------------------------------------------- /vige-api/vige/tests/test_db.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from flask_migrate import downgrade 3 | 4 | 5 | @pytest.fixture 6 | def hijack_db(db, monkeypatch, db_session): 7 | # make alembic run with our test db connection 8 | monkeypatch.setattr(db.engine, 'connect', db_session.get_bind) 9 | return db 10 | 11 | 12 | def test_downgrade(hijack_db): 13 | downgrade() 14 | 15 | -------------------------------------------------------------------------------- /vige-web/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | /tests/e2e/videos/ 6 | /tests/e2e/screenshots/ 7 | 8 | # local env files 9 | .env.local 10 | .env.*.local 11 | 12 | # Log files 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw* 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__/ 3 | *.py[cod] 4 | *.egg-info/ 5 | .pytest_cache/ 6 | .venv/ 7 | venv/ 8 | 9 | # Node 10 | node_modules/ 11 | dist/ 12 | coverage/ 13 | 14 | # Editor/OS 15 | .DS_Store 16 | .idea/ 17 | .vscode/ 18 | 19 | # Env 20 | .env 21 | *.env 22 | vige-api/vige/local_config.env 23 | 24 | # Docker 25 | *.log 26 | 27 | .idea 28 | .idea/ 29 | .DS_Store 30 | .project -------------------------------------------------------------------------------- /vige-wechat/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | /tests/e2e/videos/ 6 | /tests/e2e/screenshots/ 7 | 8 | # local env files 9 | .env.local 10 | .env.*.local 11 | 12 | # Log files 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw* 25 | -------------------------------------------------------------------------------- /vige-bo/tests/e2e/plugins/index.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/guides/guides/plugins-guide.html 2 | 3 | module.exports = (on, config) => Object.assign({}, config, { 4 | fixturesFolder: 'tests/e2e/fixtures', 5 | integrationFolder: 'tests/e2e/specs', 6 | screenshotsFolder: 'tests/e2e/screenshots', 7 | videosFolder: 'tests/e2e/videos', 8 | supportFile: 'tests/e2e/support/index.js' 9 | }) 10 | -------------------------------------------------------------------------------- /vige-bo/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import user from './module/user' 5 | import app from './module/app' 6 | 7 | Vue.use(Vuex) 8 | 9 | export default new Vuex.Store({ 10 | state: { 11 | // 12 | }, 13 | mutations: { 14 | // 15 | }, 16 | actions: { 17 | // 18 | }, 19 | modules: { 20 | user, 21 | app 22 | } 23 | }) 24 | -------------------------------------------------------------------------------- /vige-api/vige/api/fixtures.py: -------------------------------------------------------------------------------- 1 | from . import router as app 2 | 3 | _fixtures = {} 4 | 5 | 6 | def register_enum(enum, func=None): 7 | _fixtures[enum.__name__] = [ 8 | e.dump() for e in enum if (True if func is None else func(e)) 9 | ] 10 | 11 | 12 | @app.get('/admin/fixtures') 13 | def get_fixtures(): 14 | return dict( 15 | success=True, 16 | fixtures=_fixtures, 17 | ) 18 | -------------------------------------------------------------------------------- /vige-api/vige/api/users/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from vige.test_utils import BaseFactory 3 | from .models import User 4 | 5 | 6 | class UserFactory(BaseFactory): 7 | class Meta: 8 | model = User 9 | 10 | openid = factory.Sequence(lambda n: f'wx-{n}') 11 | mobile = factory.Faker('phone_number') 12 | profile = factory.Dict(dict( 13 | name=factory.Faker('name'), 14 | )) 15 | 16 | -------------------------------------------------------------------------------- /vige-wechat/tests/e2e/plugins/index.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/guides/guides/plugins-guide.html 2 | 3 | module.exports = (on, config) => { 4 | return Object.assign({}, config, { 5 | fixturesFolder: 'tests/e2e/fixtures', 6 | integrationFolder: 'tests/e2e/specs', 7 | screenshotsFolder: 'tests/e2e/screenshots', 8 | videosFolder: 'tests/e2e/videos', 9 | supportFile: 'tests/e2e/support/index.js' 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /vige-web/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | '@vue/standard' 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 13 | }, 14 | parserOptions: { 15 | parser: 'babel-eslint' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /vige-wechat/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | '@vue/standard' 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 13 | }, 14 | parserOptions: { 15 | parser: 'babel-eslint' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /vige-bo/tests/unit/HelloWorld.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { shallow } from '@vue/test-utils' 3 | import HelloWorld from '@/components/HelloWorld.vue' 4 | 5 | describe('HelloWorld.vue', () => { 6 | it('renders props.msg when passed', () => { 7 | const msg = 'new message' 8 | const wrapper = shallow(HelloWorld, { 9 | propsData: { msg } 10 | }) 11 | expect(wrapper.text()).to.include(msg) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /vige-bo/src/view/error-page/404.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | -------------------------------------------------------------------------------- /vige-bo/src/view/error-page/500.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | -------------------------------------------------------------------------------- /vige-wechat/tests/unit/HelloWorld.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { shallowMount } from '@vue/test-utils' 3 | import HelloWorld from '@/components/HelloWorld.vue' 4 | 5 | describe('HelloWorld.vue', () => { 6 | it('renders props.msg when passed', () => { 7 | const msg = 'new message' 8 | const wrapper = shallowMount(HelloWorld, { 9 | propsData: { msg } 10 | }) 11 | expect(wrapper.text()).to.include(msg) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /vige-bo/src/view/error-page/401.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | -------------------------------------------------------------------------------- /vige-bo/src/view/error-page/403.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | -------------------------------------------------------------------------------- /vige-bo/src/view/main/components/header-bar/sider-trigger/sider-trigger.less: -------------------------------------------------------------------------------- 1 | .trans{ 2 | transition: transform .2s ease; 3 | } 4 | @size: 40px; 5 | .sider-trigger-a{ 6 | padding: 6px; 7 | width: @size; 8 | height: @size; 9 | display: inline-block; 10 | text-align: center; 11 | color: #5c6b77; 12 | margin-top: 12px; 13 | i{ 14 | .trans; 15 | vertical-align: top; 16 | } 17 | &.collapsed i{ 18 | transform: rotateZ(90deg); 19 | .trans; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /vige-web/src/views/main/components/header-bar/sider-trigger/sider-trigger.less: -------------------------------------------------------------------------------- 1 | .trans{ 2 | transition: transform .2s ease; 3 | } 4 | @size: 40px; 5 | .sider-trigger-a{ 6 | padding: 6px; 7 | width: @size; 8 | height: @size; 9 | display: inline-block; 10 | text-align: center; 11 | color: #5c6b77; 12 | margin-top: 12px; 13 | i{ 14 | .trans; 15 | vertical-align: top; 16 | } 17 | &.collapsed i{ 18 | transform: rotateZ(90deg); 19 | .trans; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/vige-api" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "npm" 8 | directory: "/vige-web" 9 | schedule: 10 | interval: "weekly" 11 | - package-ecosystem: "npm" 12 | directory: "/vige-bo" 13 | schedule: 14 | interval: "weekly" 15 | - package-ecosystem: "npm" 16 | directory: "/vige-wechat" 17 | schedule: 18 | interval: "weekly" 19 | 20 | -------------------------------------------------------------------------------- /vige-wechat/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 25 | -------------------------------------------------------------------------------- /vige-bo/src/view/main/components/side-menu/item-mixin.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | parentItem: { 4 | type: Object, 5 | default: () => {} 6 | }, 7 | theme: String, 8 | iconSize: Number 9 | }, 10 | computed: { 11 | parentName () { 12 | return this.parentItem.name 13 | }, 14 | children () { 15 | return this.parentItem.children 16 | }, 17 | textColor () { 18 | return this.theme === 'dark' ? '#fff' : '#495060' 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /vige-bo/src/components/Tinymce/toolbar.js: -------------------------------------------------------------------------------- 1 | // Here is a list of the toolbar 2 | // Detail list see https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols 3 | 4 | const toolbar = ['searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample', 'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media Table emoticons forecolor backcolor fullscreen'] 5 | 6 | export default toolbar 7 | -------------------------------------------------------------------------------- /vige-bo/src/locale/lang/zh-CN.js: -------------------------------------------------------------------------------- 1 | export default { 2 | components: '组件', 3 | count_to_page: '数字渐变', 4 | tables_page: '多功能表格', 5 | split_pane_page: '分割窗口', 6 | markdown_page: 'Markdown编辑器', 7 | editor_page: '富文本编辑器', 8 | icons_page: '自定义图标', 9 | img_cropper_page: '图片编辑器', 10 | update: '上传数据', 11 | join_page: 'QQ群', 12 | doc: '文档', 13 | update_table_page: '上传CSV文件', 14 | update_paste_page: '粘贴表格数据', 15 | multilevel: '多级菜单', 16 | directive_page: '指令', 17 | level_1: 'level-1', 18 | level_2: 'level-2', 19 | level_2_1: 'level-2-1' 20 | } 21 | -------------------------------------------------------------------------------- /vige-bo/src/locale/lang/zh-TW.js: -------------------------------------------------------------------------------- 1 | export default { 2 | components: '组件', 3 | count_to_page: '数字渐变', 4 | tables_page: '多功能表格', 5 | split_pane_page: '分割窗口', 6 | markdown_page: 'Markdown編輯器', 7 | editor_page: '富文本編輯器', 8 | icons_page: '自定義圖標', 9 | img_cropper_page: '圖片編輯器', 10 | update: '上傳數據', 11 | join_page: 'QQ群', 12 | doc: '文檔', 13 | update_table_page: '上傳CSV文件', 14 | update_paste_page: '粘貼表格數據', 15 | multilevel: '多级菜单', 16 | directive_page: '指令', 17 | level_1: 'level-1', 18 | level_2: 'level-2', 19 | level_2_1: 'level-2-1' 20 | } 21 | -------------------------------------------------------------------------------- /vige-web/src/locale/lang/zh-CN.js: -------------------------------------------------------------------------------- 1 | export default { 2 | components: '组件', 3 | count_to_page: '数字渐变', 4 | tables_page: '多功能表格', 5 | split_pane_page: '分割窗口', 6 | markdown_page: 'Markdown编辑器', 7 | editor_page: '富文本编辑器', 8 | icons_page: '自定义图标', 9 | img_cropper_page: '图片编辑器', 10 | update: '上传数据', 11 | join_page: 'QQ群', 12 | doc: '文档', 13 | update_table_page: '上传CSV文件', 14 | update_paste_page: '粘贴表格数据', 15 | multilevel: '多级菜单', 16 | directive_page: '指令', 17 | level_1: 'level-1', 18 | level_2: 'level-2', 19 | level_2_1: 'level-2-1' 20 | } 21 | -------------------------------------------------------------------------------- /vige-web/src/locale/lang/zh-TW.js: -------------------------------------------------------------------------------- 1 | export default { 2 | components: '组件', 3 | count_to_page: '数字渐变', 4 | tables_page: '多功能表格', 5 | split_pane_page: '分割窗口', 6 | markdown_page: 'Markdown編輯器', 7 | editor_page: '富文本編輯器', 8 | icons_page: '自定義圖標', 9 | img_cropper_page: '圖片編輯器', 10 | update: '上傳數據', 11 | join_page: 'QQ群', 12 | doc: '文檔', 13 | update_table_page: '上傳CSV文件', 14 | update_paste_page: '粘貼表格數據', 15 | multilevel: '多级菜单', 16 | directive_page: '指令', 17 | level_1: 'level-1', 18 | level_2: 'level-2', 19 | level_2_1: 'level-2-1' 20 | } 21 | -------------------------------------------------------------------------------- /vige-bo/src/components/tables/extend-table-col.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 28 | 29 | -------------------------------------------------------------------------------- /vige-api/vige/api/settings/models.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Integer, Unicode 2 | from sqlalchemy.orm import Mapped, mapped_column 3 | from typing import Optional 4 | from ...db import CRUDMixin, Base 5 | 6 | 7 | class Settings(CRUDMixin): 8 | __tablename__ = 'settings' 9 | 10 | key: Mapped[str] = mapped_column(Unicode, unique=True, nullable=False) 11 | value: Mapped[Optional[str]] = mapped_column(Unicode) 12 | 13 | def dump(self): 14 | return dict( 15 | id=self.id, 16 | key=self.key, 17 | value=self.value, 18 | ) 19 | -------------------------------------------------------------------------------- /vige-wechat/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueAxios from 'vue-axios' 3 | import Mint from 'mint-ui' 4 | import App from './App.vue' 5 | import router from './router' 6 | import store from './store' 7 | import { api } from './libs/api' 8 | import WechatPlugin from './plugin/wechat' 9 | 10 | import 'mint-ui/lib/style.css' 11 | 12 | Vue.config.productionTip = false 13 | Vue.use(Mint) 14 | Vue.use(VueAxios, api) 15 | Vue.use(require('vue-wechat-title')) 16 | Vue.use(WechatPlugin) 17 | 18 | new Vue({ 19 | router, 20 | store, 21 | render: h => h(App) 22 | }).$mount('#app') 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug 报告 2 | description: 反馈项目中的缺陷 3 | labels: [bug] 4 | body: 5 | - type: textarea 6 | id: desc 7 | attributes: 8 | label: 问题描述 9 | description: 清晰描述问题现象、预期行为与重现步骤 10 | validations: 11 | required: true 12 | - type: textarea 13 | id: steps 14 | attributes: 15 | label: 复现步骤 16 | value: | 17 | 1. 18 | 2. 19 | 3. 20 | - type: input 21 | id: version 22 | attributes: 23 | label: 版本/提交 24 | - type: textarea 25 | id: logs 26 | attributes: 27 | label: 日志/截图 28 | 29 | -------------------------------------------------------------------------------- /vige-bo/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "babel-eslint", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "env": { 9 | "browser": true, 10 | "node": true, 11 | "es6": true 12 | }, 13 | "extends": "eslint:recommended", 14 | "plugins": ["html", "standard"], 15 | "rules": { 16 | "indent": ["error", 2, { 17 | "SwitchCase": 1 18 | }], 19 | "quotes": ["error", "single"], 20 | "semi": 0, 21 | "no-console": ["error"] 22 | } 23 | } -------------------------------------------------------------------------------- /vige-bo/src/components/Tinymce/plugins.js: -------------------------------------------------------------------------------- 1 | // Any plugins you want to use has to be imported 2 | // Detail plugins list see https://www.tinymce.com/docs/plugins/ 3 | // Custom builds see https://www.tinymce.com/download/custom-builds/ 4 | 5 | const plugins = ['advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus template textcolor textpattern visualblocks visualchars wordcount'] 6 | 7 | export default plugins 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## 贡献指南(精简版) 2 | 3 | ### 本地运行 4 | - 后端:`cd vige-api && pipenv sync --dev && pipenv run uvicorn vige.app:app --reload` 5 | - 前端:`cd vige-web && yarn && yarn dev`(BO/Wechat 同理) 6 | - 数据库/缓存:可使用 `docker-compose up -d postgres redis` 7 | 8 | ### 提交规范 9 | - 建议使用 Conventional Commits:`feat: ...`、`fix: ...`、`chore: ...` 10 | - 新功能/修复需附最小验证说明(或截图) 11 | 12 | ### PR 流程 13 | - 从 `master` 切分支; 14 | - 保持 PR 小而清晰; 15 | - 通过 CI(编译/导入检查)后由维护者合并。 16 | 17 | ### 代码规范要点 18 | - 后端:Pydantic 2 语法(pattern、json_schema_extra)、统一错误响应、Session 隔离、ProfileMixin 两步法; 19 | - 前端:避免 ES2020+ 语法(?.、??、import()),统一 axios 响应处理。 20 | 21 | -------------------------------------------------------------------------------- /vige-api/vige/migrations/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = ${repr(up_revision)} 14 | down_revision = ${repr(down_revision)} 15 | branch_labels = ${repr(branch_labels)} 16 | depends_on = ${repr(depends_on)} 17 | 18 | 19 | def upgrade(): 20 | ${upgrades if upgrades else "pass"} 21 | 22 | 23 | def downgrade(): 24 | ${downgrades if downgrades else "pass"} 25 | -------------------------------------------------------------------------------- /vige-bo/src/view/main/components/side-menu/mixin.js: -------------------------------------------------------------------------------- 1 | import CommonIcon from '_c/common-icon' 2 | export default { 3 | components: { 4 | CommonIcon 5 | }, 6 | methods: { 7 | showTitle (item) { 8 | return this.$config.useI18n ? this.$t(item.name) : ((item.meta && item.meta.title) || item.name) 9 | }, 10 | showChildren (item) { 11 | return item.children && (item.children.length > 1 || (item.meta && item.meta.showAlways)) 12 | }, 13 | getNameOrHref (item, children0) { 14 | return item.href ? `isTurnByHref_${item.href}` : (children0 ? item.children[0].name : item.name) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /vige-bo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | iview-admin 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /vige-web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Website 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /vige-bo/src/locale/lang/en-US.js: -------------------------------------------------------------------------------- 1 | export default { 2 | components: 'Components', 3 | count_to_page: 'Count-to', 4 | tables_page: 'Table', 5 | split_pane_page: 'Split-pane', 6 | markdown_page: 'Markdown-editor', 7 | editor_page: 'Rich-Text-Editor', 8 | icons_page: 'Custom-icon', 9 | img_cropper_page: 'Image-editor', 10 | update: 'Update', 11 | doc: 'Document', 12 | join_page: 'QQ Group', 13 | update_table_page: 'Update .CSV', 14 | update_paste_page: 'Paste Table Data', 15 | multilevel: 'multilevel', 16 | directive_page: 'Directive', 17 | level_1: 'level-1', 18 | level_2: 'level-2', 19 | level_2_1: 'level-2-1' 20 | } 21 | -------------------------------------------------------------------------------- /vige-web/src/locale/lang/en-US.js: -------------------------------------------------------------------------------- 1 | export default { 2 | components: 'Components', 3 | count_to_page: 'Count-to', 4 | tables_page: 'Table', 5 | split_pane_page: 'Split-pane', 6 | markdown_page: 'Markdown-editor', 7 | editor_page: 'Rich-Text-Editor', 8 | icons_page: 'Custom-icon', 9 | img_cropper_page: 'Image-editor', 10 | update: 'Update', 11 | doc: 'Document', 12 | join_page: 'QQ Group', 13 | update_table_page: 'Update .CSV', 14 | update_paste_page: 'Paste Table Data', 15 | multilevel: 'multilevel', 16 | directive_page: 'Directive', 17 | level_1: 'level-1', 18 | level_2: 'level-2', 19 | level_2_1: 'level-2-1' 20 | } 21 | -------------------------------------------------------------------------------- /vige-wechat/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Wechat Title 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /vige-bo/src/components/icons/icons.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 32 | 33 | 36 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.6.0 4 | hooks: 5 | - id: end-of-file-fixer 6 | - id: trailing-whitespace 7 | - repo: https://github.com/astral-sh/ruff-pre-commit 8 | rev: v0.5.7 9 | hooks: 10 | - id: ruff 11 | args: ["--fix"] 12 | additional_dependencies: [] 13 | - repo: https://github.com/psf/black 14 | rev: 24.4.2 15 | hooks: 16 | - id: black 17 | language_version: python3.10 18 | - repo: https://github.com/pycqa/isort 19 | rev: 5.13.2 20 | hooks: 21 | - id: isort 22 | args: ["--profile", "black"] 23 | 24 | -------------------------------------------------------------------------------- /vige-web/src/components/icons/icons.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 32 | 33 | 36 | -------------------------------------------------------------------------------- /vige-web/README.md: -------------------------------------------------------------------------------- 1 | # wechat 2 | 3 | ## Project setup 4 | ``` 5 | yarn install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | yarn run dev 11 | ``` 12 | 13 | ### Login a development account 14 | 15 | http://localhost:port/v1/auth?openid=anyRandomString 16 | 17 | It'll create an account if it not exists.See `/v1/auth` api for details. 18 | 19 | ### Compiles and minifies for production 20 | ``` 21 | yarn run build 22 | ``` 23 | 24 | ### Lints and fixes files 25 | ``` 26 | yarn run lint 27 | ``` 28 | 29 | ### Run your unit tests 30 | ``` 31 | yarn run test:unit 32 | ``` 33 | 34 | ### Run your end-to-end tests 35 | ``` 36 | yarn run test:e2e 37 | ``` 38 | -------------------------------------------------------------------------------- /vige-wechat/README.md: -------------------------------------------------------------------------------- 1 | # wechat 2 | 3 | ## Project setup 4 | ``` 5 | yarn install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | yarn run dev 11 | ``` 12 | 13 | ### Login a development account 14 | 15 | http://localhost:port/v1/auth?openid=anyRandomString 16 | 17 | It'll create an account if it not exists.See `/v1/auth` api for details. 18 | 19 | ### Compiles and minifies for production 20 | ``` 21 | yarn run build 22 | ``` 23 | 24 | ### Lints and fixes files 25 | ``` 26 | yarn run lint 27 | ``` 28 | 29 | ### Run your unit tests 30 | ``` 31 | yarn run test:unit 32 | ``` 33 | 34 | ### Run your end-to-end tests 35 | ``` 36 | yarn run test:e2e 37 | ``` 38 | -------------------------------------------------------------------------------- /vige-api/vige/api/settings/forms.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from pydantic import BaseModel, field_validator, ValidationError 3 | 4 | from .settings import Settings 5 | 6 | 7 | class ConfigForm(BaseModel): 8 | key: str 9 | value: Optional[str] 10 | 11 | @field_validator('key') 12 | def validate_key(cls, v): 13 | if not v: 14 | raise ValidationError('配置项不能为空') 15 | if v not in Settings.__dict__: 16 | raise ValidationError('该项配置不合法') 17 | return v 18 | 19 | @field_validator('value') 20 | def validate_value(cls, v): 21 | if not v: 22 | raise ValidationError('配置值不能为空') 23 | return v 24 | -------------------------------------------------------------------------------- /vige-bo/src/view/error-page/error-content.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | -------------------------------------------------------------------------------- /vige-api/vige/api/bo_user/factories.py: -------------------------------------------------------------------------------- 1 | import factory 2 | from vige.test_utils import BaseFactory 3 | from .security import generate_password_hash 4 | from .models import BoRole, BoUser 5 | 6 | DEFAULT_PASSWORD = '123456' 7 | 8 | 9 | class BoRoleFactory(BaseFactory): 10 | class Meta: 11 | model = BoRole 12 | 13 | name = factory.Sequence(lambda n: f"role {n}") 14 | 15 | 16 | class BoUserFactory(BaseFactory): 17 | class Meta: 18 | model = BoUser 19 | username = factory.Faker('name') 20 | password = factory.LazyFunction( 21 | lambda: generate_password_hash(DEFAULT_PASSWORD)) 22 | mobile = factory.Sequence(lambda n: '1%010d' % n) 23 | role = factory.SubFactory(BoRoleFactory) 24 | -------------------------------------------------------------------------------- /vige-bo/src/view/main/components/header-bar/sider-trigger/sider-trigger.vue: -------------------------------------------------------------------------------- 1 | 4 | 25 | 28 | -------------------------------------------------------------------------------- /vige-web/src/views/main/components/header-bar/sider-trigger/sider-trigger.vue: -------------------------------------------------------------------------------- 1 | 4 | 25 | 28 | -------------------------------------------------------------------------------- /vige-api/vige/api/wechat/form.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field 2 | from typing import Optional 3 | 4 | 5 | class WechatDecryptForm(BaseModel): 6 | """微信解密表单""" 7 | iv: str = Field(..., description="微信加密向量", min_length=1) 8 | encryptedData: str = Field(..., description="微信加密数据", min_length=1) 9 | code: str = Field(..., description="微信code", min_length=1) 10 | bind_id: Optional[str] = Field(None, description="绑定用户ID") 11 | 12 | class Config: 13 | json_schema_extra = { 14 | "example": { 15 | "iv": "iv_example", 16 | "encryptedData": "encrypted_data_example", 17 | "code": "wx_code_example", 18 | "bind_id": "123" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /vige-api/vige/huey_config.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | import logging 3 | 4 | from huey import RedisHuey, crontab 5 | 6 | from .config import config 7 | 8 | 9 | huey = RedisHuey( 10 | name=config.HUEY_NAME, 11 | host=config.REDIS_HOST, 12 | port=config.REDIS_PORT, 13 | db=config.REDIS_DB, 14 | result_store=False, 15 | store_errors=False, 16 | ) 17 | logger = logging.getLogger() 18 | 19 | 20 | # convinience crontab definitions 21 | def cron_daily(hour='17', minute='0'): 22 | return crontab(hour=hour, minute=minute) 23 | 24 | 25 | def app_context(f): 26 | @wraps(f) 27 | def wrapper(*args, **kwargs): 28 | # FastAPI没有Flask的app_context,但你可以在这里初始化一些资源 29 | return f(*args, **kwargs) 30 | return wrapper 31 | -------------------------------------------------------------------------------- /vige-bo/tests/e2e/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /vige-wechat/tests/e2e/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /vige-api/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | PORT=${PORT:-8000} 6 | WEB_CONCURRENCY=${WEB_CONCURRENCY:-4} 7 | WEB_WORKER_TYPE=${WEB_WORKER_TYPE:-sync} 8 | WORKER_CONCURRENCY=${WORKER_CONCURRENCY:-4} 9 | WORKER_WORKER_TYPE=${WORKER_WORKER_TYPE:-process} 10 | 11 | case $1 in 12 | web) 13 | alembic upgrade head 14 | exec uvicorn vige.app:app --host 0.0.0.0 --port $PORT --workers 6 15 | ;; 16 | socketio) 17 | exec python run_sio.py 18 | ;; 19 | worker) 20 | exec huey_consumer -w $WORKER_CONCURRENCY -k $WORKER_WORKER_TYPE vige.huey_app.huey 21 | ;; 22 | -h) 23 | echo "run components: [web|worker], or any other shell command" 24 | ;; 25 | *) 26 | exec "$@" 27 | ;; 28 | esac 29 | -------------------------------------------------------------------------------- /vige-bo/src/mixins/formErrorMixin.js: -------------------------------------------------------------------------------- 1 | import { get } from 'lodash' 2 | 3 | 4 | export const FormErrorMixin = { 5 | 6 | data () { 7 | return { 8 | serverErrors: {}, 9 | } 10 | }, 11 | 12 | methods: { 13 | setFormErrors (error) { 14 | let errorInfo = get(error, 'response.data.error.errors') 15 | if (errorInfo) { 16 | Object.keys(errorInfo).forEach(key => { 17 | errorInfo[key] = errorInfo[key].join(',') 18 | }) 19 | this.serverErrors = Object.assign(this.serverErrors, errorInfo) 20 | } 21 | }, 22 | 23 | clearErrors () { 24 | if (this.serverErrors) { 25 | Object.keys(this.serverErrors).forEach(key => { 26 | this.serverErrors[key] = null 27 | }) 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /vige-bo/src/view/login/login.less: -------------------------------------------------------------------------------- 1 | .login{ 2 | width: 100%; 3 | height: 100%; 4 | background-image: url('../../assets/images/login-bg.jpg'); 5 | background-size: cover; 6 | background-position: center; 7 | position: relative; 8 | &-con{ 9 | position: absolute; 10 | right: 160px; 11 | top: 50%; 12 | transform: translateY(-60%); 13 | width: 300px; 14 | &-header{ 15 | font-size: 16px; 16 | font-weight: 300; 17 | text-align: center; 18 | padding: 30px 0; 19 | } 20 | .form-con{ 21 | padding: 10px 0 0; 22 | } 23 | .login-tip{ 24 | font-size: 10px; 25 | text-align: center; 26 | color: #c3c3c3; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /vige-api/vige/api/settings/settings.py: -------------------------------------------------------------------------------- 1 | from .fields import * 2 | from .base import SettingsBase 3 | 4 | 5 | class Settings(SettingsBase): 6 | """Live settings. 7 | 8 | Define here, use `dotalk.api.utils.settings`. 9 | 10 | Unlike hard static Flask config, settings are stored in database and loaded 11 | on every request, suitable for frequently-changed flexible global settings. 12 | """ 13 | enable_two_fa_login = BooleanField( 14 | False, name='启用 2fa 登录', 15 | desc='启用后,后台用户登录需要进行手机验证码二次认证', type='switch') 16 | enable_two_fa_debug_mode = BooleanField( 17 | False, name='启用 2fa Debug 模式', 18 | desc='启用后,888888 可用做万能验证码', type='switch') 19 | wechat_event_token = StringField( 20 | '', name='微信验证 Token', 21 | desc='用于验证微信通知发送地址的 token', type='input' 22 | ) 23 | -------------------------------------------------------------------------------- /vige-bo/src/components/tables/invalid-col.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 22 | 23 | 34 | -------------------------------------------------------------------------------- /vige-bo/src/components/tables/poptip-btn.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 39 | 40 | -------------------------------------------------------------------------------- /vige-bo/src/view/single-page/home/home.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 29 | 30 | 40 | -------------------------------------------------------------------------------- /vige-api/vige/migrations/versions/5242cbfe15aa_disable_role.py: -------------------------------------------------------------------------------- 1 | """disable role 2 | 3 | Revision ID: 5242cbfe15aa 4 | Revises: a468e4908c9a 5 | Create Date: 2018-08-21 18:45:57.748453 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '5242cbfe15aa' 14 | down_revision = 'a468e4908c9a' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('bo_roles', sa.Column('disabled_at', sa.DateTime(), nullable=True)) 22 | # ### end Alembic commands ### 23 | 24 | 25 | def downgrade(): 26 | # ### commands auto generated by Alembic - please adjust! ### 27 | op.drop_column('bo_roles', 'disabled_at') 28 | # ### end Alembic commands ### 29 | -------------------------------------------------------------------------------- /vige-api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10 2 | 3 | ARG PIP_INDEX_URL 4 | ENV PIP_INDEX_URL=${PIP_INDEX_URL:-https://mirrors.aliyun.com/pypi/simple/} 5 | 6 | RUN mkdir /vige 7 | # https://github.com/pypa/pipenv/issues/2924 8 | # https://github.com/pypa/pipenv/issues/2871 9 | RUN pip install pipenv==2023.11.14 10 | RUN pip install pip==20.2.3 11 | 12 | COPY Pipfile /vige 13 | COPY Pipfile.lock /vige 14 | WORKDIR /vige 15 | RUN pipenv install --system --deploy 16 | 17 | COPY . /vige 18 | WORKDIR /vige 19 | RUN pip install -e . 20 | 21 | EXPOSE 8000 22 | ENV WEB_CONCURRENCY=4 WORKER_CONCURRENCY=4 23 | VOLUME ["/vige/local_config.env"] 24 | ENTRYPOINT ["./entrypoint.sh"] 25 | CMD ["web"] 26 | 27 | # run web: 28 | # docker run vige web 29 | # run worker: 30 | # docker run vige worker 31 | # enter container 32 | # docker exec -it vige-container bash 33 | -------------------------------------------------------------------------------- /vige-api/vige/api/__init__.py: -------------------------------------------------------------------------------- 1 | import decimal 2 | from datetime import date, datetime 3 | from fastapi import FastAPI, Request, HTTPException, APIRouter 4 | from fastapi.responses import JSONResponse 5 | from pydantic import BaseModel 6 | import json 7 | 8 | 9 | router = APIRouter(prefix="/v1") 10 | 11 | 12 | # noinspection PyShadowingNames 13 | def install(app: FastAPI): 14 | from .jwt import install 15 | install() 16 | 17 | from .bo_user import api 18 | 19 | from .users import api 20 | from .settings import api 21 | from .wechat import api 22 | from . import fixtures 23 | from .media import api 24 | 25 | app.include_router(router) 26 | 27 | # from .errors import handle_http_error 28 | # for code in default_exceptions: 29 | # app.errorhandler(code)(handle_http_error) 30 | # 31 | # app.json_encoder = JSONEncoder 32 | -------------------------------------------------------------------------------- /vige-api/vige/api/media/forms.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, File, UploadFile, HTTPException, Form 2 | from typing import Optional 3 | from pydantic import BaseModel, field_validator 4 | 5 | ALLOWED_IMAGE_EXTENSIONS = {"png", "jpg", "jpeg", "gif"} 6 | ALLOWED_AUDIO_EXTENSIONS = {"mp3", "m4a"} 7 | 8 | 9 | class ImageForm(BaseModel): 10 | image: UploadFile 11 | 12 | @field_validator('image') 13 | def validate_image(cls, v, values): 14 | if not v.filename.split('.')[-1] in ALLOWED_IMAGE_EXTENSIONS: 15 | raise ValueError('图片格式错误') 16 | return v 17 | 18 | 19 | class AudioForm(BaseModel): 20 | audio: UploadFile 21 | 22 | @field_validator('audio') 23 | def validate_audio(cls, v, values): 24 | if not v.filename.split('.')[-1] in ALLOWED_AUDIO_EXTENSIONS: 25 | raise ValueError('音频格式错误') 26 | return v 27 | 28 | -------------------------------------------------------------------------------- /vige-bo/src/components/tables/actions-col.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 25 | 26 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | postgres: 5 | image: postgres:14 6 | environment: 7 | POSTGRES_DB: vige 8 | POSTGRES_USER: postgres 9 | POSTGRES_PASSWORD: postgres 10 | ports: 11 | - "5432:5432" 12 | healthcheck: 13 | test: ["CMD-SHELL", "pg_isready -U postgres"] 14 | interval: 10s 15 | timeout: 5s 16 | retries: 5 17 | 18 | redis: 19 | image: redis:6 20 | ports: 21 | - "6379:6379" 22 | 23 | api: 24 | build: 25 | context: ./vige-api 26 | environment: 27 | - PORT=8000 28 | volumes: 29 | - ./vige-api/vige/local_config.env:/vige/vige/local_config.env 30 | depends_on: 31 | postgres: 32 | condition: service_healthy 33 | redis: 34 | condition: service_started 35 | ports: 36 | - "8000:8000" 37 | command: web 38 | 39 | -------------------------------------------------------------------------------- /vige-bo/src/view/error-page/back-btn-group.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 39 | -------------------------------------------------------------------------------- /vige-bo/src/view/main/components/side-menu/side-menu.less: -------------------------------------------------------------------------------- 1 | .side-menu-wrapper{ 2 | user-select: none; 3 | .menu-collapsed{ 4 | padding-top: 10px; 5 | 6 | .ivu-dropdown{ 7 | width: 100%; 8 | .ivu-dropdown-rel a{ 9 | width: 100%; 10 | } 11 | } 12 | .ivu-tooltip{ 13 | width: 100%; 14 | .ivu-tooltip-rel{ 15 | width: 100%; 16 | } 17 | .ivu-tooltip-popper .ivu-tooltip-content{ 18 | .ivu-tooltip-arrow{ 19 | border-right-color: #fff; 20 | } 21 | .ivu-tooltip-inner{ 22 | background: #fff; 23 | color: #495060; 24 | } 25 | } 26 | } 27 | 28 | 29 | } 30 | a.drop-menu-a{ 31 | display: inline-block; 32 | padding: 6px 15px; 33 | width: 100%; 34 | text-align: center; 35 | color: #495060; 36 | } 37 | } 38 | .menu-title{ 39 | padding-left: 6px; 40 | } 41 | -------------------------------------------------------------------------------- /vige-wechat/src/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import { get } from 'lodash' 4 | import { api } from './libs/api' 5 | 6 | Vue.use(Vuex) 7 | 8 | export default new Vuex.Store({ 9 | state: { 10 | loading: false, 11 | auth: { 12 | user: {} 13 | } 14 | }, 15 | mutations: { 16 | setLoading (state, value) { 17 | state.loading = value 18 | }, 19 | 20 | setUser (state, user) { 21 | state.auth.user = Object.assign({}, user || {}) 22 | } 23 | }, 24 | 25 | getters: { 26 | user: state => { 27 | return state.auth.user 28 | }, 29 | 30 | boundBoUser: state => { 31 | return get(state, 'auth.user.bo_user') 32 | } 33 | }, 34 | 35 | actions: { 36 | async getUser (context) { 37 | let resp = await api.get('/users/me') 38 | context.commit('setUser', resp.data.user) 39 | } 40 | } 41 | }) 42 | -------------------------------------------------------------------------------- /vige-wechat/src/views/auth.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 40 | -------------------------------------------------------------------------------- /vige-wechat/src/views/bo-workbench.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 37 | 38 | 49 | -------------------------------------------------------------------------------- /vige-bo/tests/e2e/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /vige-wechat/tests/e2e/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /vige-bo/src/app.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 25 | 26 | 33 | 34 | 49 | -------------------------------------------------------------------------------- /vige-bo/src/components/tables/table-importer-mixin.js: -------------------------------------------------------------------------------- 1 | import InvalidCol from './invalid-col' 2 | 3 | export const TableImporterMixin = { 4 | components: { 5 | InvalidCol 6 | }, 7 | 8 | methods: { 9 | renderInvalidCell (h, params) { 10 | if (params.row.errors[params.column.key]) { 11 | return h(InvalidCol, { 12 | props: { 13 | content: params.row[params.column.key], 14 | error: params.row.errors[params.column.key] 15 | } 16 | }) 17 | } else { 18 | return h('span', {}, params.row[params.column.key]) 19 | } 20 | }, 21 | 22 | composeInvalidData (invalidDatas) { 23 | let result = invalidDatas.map(row => { 24 | let cellClasses = {} 25 | for (let col in row.errors) { 26 | cellClasses[col] = row.errors[col] ? 'invalid-cell' : '' 27 | } 28 | return Object.assign({}, row, {cellClassName: cellClasses}) 29 | }) 30 | return result 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /vige-api/setup.py: -------------------------------------------------------------------------------- 1 | from io import open 2 | 3 | from setuptools import find_packages, setup 4 | 5 | with open('README.md', 'r', encoding='utf-8') as f: 6 | readme = f.read() 7 | 8 | REQUIRES = [] 9 | 10 | setup( 11 | name='vige', 12 | version='0.1.0', 13 | description='', 14 | long_description=readme, 15 | author='versus', 16 | maintainer='versus', 17 | license='MIT/Apache-2.0', 18 | 19 | classifiers=[ 20 | 'Development Status :: 4 - Beta', 21 | 'Intended Audience :: Developers', 22 | 'License :: OSI Approved :: MIT License', 23 | 'License :: OSI Approved :: Apache Software License', 24 | 'Natural Language :: English', 25 | 'Operating System :: OS Independent', 26 | 'Programming Language :: Python :: 3.6', 27 | 'Programming Language :: Python :: Implementation :: CPython', 28 | ], 29 | 30 | install_requires=REQUIRES, 31 | tests_require=['coverage', 'pytest'], 32 | 33 | packages=find_packages(), 34 | ) 35 | -------------------------------------------------------------------------------- /vige-bo/src/components/split-pane/trigger.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 40 | 41 | 44 | -------------------------------------------------------------------------------- /vige-bo/src/view/main/components/header-bar/header-bar.vue: -------------------------------------------------------------------------------- 1 | 10 | 35 | -------------------------------------------------------------------------------- /vige-api/alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # template used to generate migration files 5 | # file_template = %%(rev)s_%%(slug)s 6 | 7 | # set to 'true' to run the environment during 8 | # the 'revision' command, regardless of autogenerate 9 | # revision_environment = false 10 | script_location = vige/migrations 11 | 12 | 13 | # Logging configuration 14 | [loggers] 15 | keys = root,sqlalchemy,alembic 16 | 17 | [handlers] 18 | keys = console 19 | 20 | [formatters] 21 | keys = generic 22 | 23 | [logger_root] 24 | level = WARN 25 | handlers = console 26 | qualname = 27 | 28 | [logger_sqlalchemy] 29 | level = WARN 30 | handlers = 31 | qualname = sqlalchemy.engine 32 | 33 | [logger_alembic] 34 | level = INFO 35 | handlers = 36 | qualname = alembic 37 | 38 | [handler_console] 39 | class = StreamHandler 40 | args = (sys.stderr,) 41 | level = NOTSET 42 | formatter = generic 43 | 44 | [formatter_generic] 45 | format = %(levelname)-5.5s [%(name)s] %(message)s 46 | datefmt = %H:%M:%S 47 | -------------------------------------------------------------------------------- /vige-api/vige/migrations/versions/26b56fd7a0cd_settings_model.py: -------------------------------------------------------------------------------- 1 | """settings model 2 | 3 | Revision ID: 26b56fd7a0cd 4 | Revises: a6a1a5eaf524 5 | Create Date: 2018-08-20 17:46:43.515797 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '26b56fd7a0cd' 14 | down_revision = 'a6a1a5eaf524' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('settings', 22 | sa.Column('id', sa.BigInteger(), nullable=False), 23 | sa.Column('key', sa.Unicode(), nullable=False), 24 | sa.Column('value', sa.Unicode(), nullable=True), 25 | sa.PrimaryKeyConstraint('id'), 26 | sa.UniqueConstraint('key') 27 | ) 28 | # ### end Alembic commands ### 29 | 30 | 31 | def downgrade(): 32 | # ### commands auto generated by Alembic - please adjust! ### 33 | op.drop_table('settings') 34 | # ### end Alembic commands ### 35 | -------------------------------------------------------------------------------- /vige-bo/src/components/common-icon/common-icon.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 39 | 40 | 43 | -------------------------------------------------------------------------------- /vige-web/src/components/common-icon/common-icon.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 39 | 40 | 43 | -------------------------------------------------------------------------------- /vige-bo/src/components/tables/confirm-button.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 47 | -------------------------------------------------------------------------------- /vige-bo/src/view/login/login.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | 17 | 41 | 42 | 45 | -------------------------------------------------------------------------------- /vige-bo/src/view/error-page/error.less: -------------------------------------------------------------------------------- 1 | .error-page{ 2 | width: 100%; 3 | height: 100%; 4 | position: relative; 5 | background: #f8f8f9; 6 | .content-con{ 7 | width: 700px; 8 | height: 600px; 9 | position: absolute; 10 | left: 50%; 11 | top: 50%; 12 | transform: translate(-50%, -60%); 13 | img{ 14 | display: block; 15 | width: 100%; 16 | height: 100%; 17 | } 18 | .text-con{ 19 | position: absolute; 20 | left: 0px; 21 | top: 0px; 22 | h4{ 23 | position: absolute; 24 | left: 0px; 25 | top: 0px; 26 | font-size: 80px; 27 | font-weight: 700; 28 | color: #348EED; 29 | } 30 | h5{ 31 | position: absolute; 32 | width: 700px; 33 | left: 0px; 34 | top: 100px; 35 | font-size: 20px; 36 | font-weight: 700; 37 | color: #67647D; 38 | } 39 | } 40 | .back-btn-group{ 41 | position: absolute; 42 | right: 0px; 43 | bottom: 20px; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /vige-bo/src/locale/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueI18n from 'vue-i18n' 3 | import customZhCn from './lang/zh-CN' 4 | import customZhTw from './lang/zh-TW' 5 | import customEnUs from './lang/en-US' 6 | import zhCnLocale from 'iview/src/locale/lang/zh-CN' 7 | import enUsLocale from 'iview/src/locale/lang/en-US' 8 | import zhTwLocale from 'iview/src/locale/lang/zh-TW' 9 | 10 | Vue.use(VueI18n) 11 | 12 | let lang = window.localStorage.lang || 'zh-CN' 13 | 14 | Vue.config.lang = lang 15 | 16 | // vue-i18n 6.x+写法 17 | Vue.locale = () => {} 18 | const messages = { 19 | 'zh-CN': Object.assign(zhCnLocale, customZhCn), 20 | 'zh-TW': Object.assign(zhTwLocale, customZhTw), 21 | 'en-US': Object.assign(enUsLocale, customEnUs) 22 | } 23 | const i18n = new VueI18n({ 24 | locale: lang, 25 | messages 26 | }) 27 | 28 | export default i18n 29 | 30 | // vue-i18n 5.x写法 31 | // Vue.locale('zh-CN', Object.assign(zhCnLocale, customZhCn)) 32 | // Vue.locale('en-US', Object.assign(zhTwLocale, customZhTw)) 33 | // Vue.locale('zh-TW', Object.assign(enUsLocale, customEnUs)) 34 | -------------------------------------------------------------------------------- /vige-web/src/locale/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueI18n from 'vue-i18n' 3 | import customZhCn from './lang/zh-CN' 4 | import customZhTw from './lang/zh-TW' 5 | import customEnUs from './lang/en-US' 6 | import zhCnLocale from 'iview/src/locale/lang/zh-CN' 7 | import enUsLocale from 'iview/src/locale/lang/en-US' 8 | import zhTwLocale from 'iview/src/locale/lang/zh-TW' 9 | 10 | Vue.use(VueI18n) 11 | 12 | let lang = window.localStorage.lang || 'zh-CN' 13 | 14 | Vue.config.lang = lang 15 | 16 | // vue-i18n 6.x+写法 17 | Vue.locale = () => {} 18 | const messages = { 19 | 'zh-CN': Object.assign(zhCnLocale, customZhCn), 20 | 'zh-TW': Object.assign(zhTwLocale, customZhTw), 21 | 'en-US': Object.assign(enUsLocale, customEnUs) 22 | } 23 | const i18n = new VueI18n({ 24 | locale: lang, 25 | messages 26 | }) 27 | 28 | export default i18n 29 | 30 | // vue-i18n 5.x写法 31 | // Vue.locale('zh-CN', Object.assign(zhCnLocale, customZhCn)) 32 | // Vue.locale('en-US', Object.assign(zhTwLocale, customZhTw)) 33 | // Vue.locale('zh-TW', Object.assign(enUsLocale, customEnUs)) 34 | -------------------------------------------------------------------------------- /vige-web/src/router/routers.js: -------------------------------------------------------------------------------- 1 | import Main from '@/views/main' 2 | 3 | /** 4 | * iview-admin中meta除了原生参数外可配置的参数: 5 | * meta: { 6 | * hideInMenu: (false) 设为true后在左侧菜单不会显示该页面选项 7 | * notCache: (false) 设为true后页面不会缓存 8 | * access: (null) 可访问该页面的权限数组,当前路由设置的权限会影响子路由 9 | * icon: (-) 该页面在左侧菜单、面包屑和标签导航处显示的图标,如果是自定义图标,需要在图标名称前加下划线'_' 10 | * } 11 | */ 12 | 13 | export default [ 14 | { 15 | path: '/login', 16 | name: 'login', 17 | meta: { 18 | title: 'Login - 登录', 19 | hideInMenu: true 20 | }, 21 | component: () => import('@/views/login.vue') 22 | }, 23 | { 24 | path: '/', 25 | name: '_home', 26 | redirect: '/home', 27 | component: Main, 28 | meta: { 29 | hideInMenu: true, 30 | hide: true, 31 | notCache: true, 32 | auth: true, 33 | }, 34 | children: [ 35 | { 36 | path: '/home', 37 | name: 'home', 38 | meta: { 39 | title: '首页', 40 | hide: true, 41 | }, 42 | component: () => import('@/views/home.vue') 43 | }, 44 | ] 45 | } 46 | ] 47 | -------------------------------------------------------------------------------- /vige-api/vige/i18n.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, Request 2 | from fastapi_babel import BabelMiddleware, BabelConfigs 3 | from starlette.middleware.base import BaseHTTPMiddleware 4 | 5 | app = FastAPI() 6 | 7 | # 配置 Babel 的默认语言和支持的语言 8 | BABEL_DEFAULT_LOCALE = 'en' 9 | BABEL_SUPPORTED_LOCALES = ['en', 'es', 'fr'] 10 | 11 | configs = BabelConfigs( 12 | ROOT_DIR=__file__, 13 | BABEL_DEFAULT_LOCALE="en", # 默认语言 14 | BABEL_TRANSLATION_DIRECTORY="lang" # 翻译文件存放的目录 15 | ) 16 | 17 | # 安装 Babel 中间件 18 | app.add_middleware( 19 | BaseHTTPMiddleware, 20 | dispatch=BabelMiddleware( 21 | app, 22 | configs 23 | ) 24 | ) 25 | 26 | 27 | @app.middleware("http") 28 | async def get_locale(request: Request, call_next): 29 | """ 30 | Middleware to determine the best match for supported locales based on the request. 31 | """ 32 | # # 获取请求中接受的语言 33 | # accept_language = request.headers.get('accept-language', '') 34 | 35 | # 将最佳匹配语言添加到请求上下文 36 | request.state.locale = BABEL_DEFAULT_LOCALE 37 | 38 | # 继续处理请求 39 | response = await call_next(request) 40 | return response 41 | 42 | 43 | -------------------------------------------------------------------------------- /vige-api/vige/migrations/versions/1804876c9b60_bind_wechat.py: -------------------------------------------------------------------------------- 1 | """bind wechat 2 | 3 | Revision ID: 1804876c9b60 4 | Revises: 5242cbfe15aa 5 | Create Date: 2018-08-29 12:03:40.569108 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '1804876c9b60' 14 | down_revision = '5242cbfe15aa' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.add_column('users', sa.Column('bo_users_id', sa.BigInteger(), nullable=True)) 22 | op.create_index(op.f('ix_users_bo_users_id'), 'users', ['bo_users_id'], unique=False) 23 | op.create_foreign_key(None, 'users', 'bo_users', ['bo_users_id'], ['id']) 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade(): 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.drop_constraint(None, 'users', type_='foreignkey') 30 | op.drop_index(op.f('ix_users_bo_users_id'), table_name='users') 31 | op.drop_column('users', 'bo_users_id') 32 | # ### end Alembic commands ### 33 | -------------------------------------------------------------------------------- /vige-wechat/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wechat", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint", 9 | "test:unit": "vue-cli-service test:unit", 10 | "test:e2e": "vue-cli-service test:e2e" 11 | }, 12 | "dependencies": { 13 | "axios": "^0.18.0", 14 | "js-cookie": "^2.2.0", 15 | "lodash": "^4.17.10", 16 | "mint-ui": "^2.2.13", 17 | "vue": "^2.5.17", 18 | "vue-axios": "^2.1.3", 19 | "vue-router": "^3.0.1", 20 | "vue-wechat-title": "^2.0.4", 21 | "vuex": "^3.0.1" 22 | }, 23 | "devDependencies": { 24 | "@vue/cli-plugin-babel": "^3.0.0", 25 | "@vue/cli-plugin-e2e-cypress": "^3.0.0", 26 | "@vue/cli-plugin-eslint": "^3.0.0", 27 | "@vue/cli-plugin-unit-mocha": "^3.0.0", 28 | "@vue/cli-service": "^3.0.0", 29 | "@vue/eslint-config-standard": "^3.0.1", 30 | "@vue/test-utils": "^1.0.0-beta.20", 31 | "chai": "^4.1.2", 32 | "less": "^3.0.4", 33 | "less-loader": "^4.1.0", 34 | "vue-template-compiler": "^2.5.17" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/frontend.yml: -------------------------------------------------------------------------------- 1 | name: Frontend CI 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'vige-web/**' 7 | - 'vige-bo/**' 8 | - 'vige-wechat/**' 9 | - '.github/workflows/frontend.yml' 10 | pull_request: 11 | paths: 12 | - 'vige-web/**' 13 | - 'vige-bo/**' 14 | - 'vige-wechat/**' 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | strategy: 20 | matrix: 21 | project: [vige-web, vige-bo, vige-wechat] 22 | node: [16] 23 | steps: 24 | - uses: actions/checkout@v4 25 | - name: Setup Node 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: ${{ matrix.node }} 29 | - name: Install deps 30 | working-directory: ${{ matrix.project }} 31 | run: | 32 | yarn install --frozen-lockfile || yarn install 33 | - name: Lint 34 | working-directory: ${{ matrix.project }} 35 | run: | 36 | yarn run lint || echo "lint warnings ignored" 37 | - name: Build 38 | working-directory: ${{ matrix.project }} 39 | run: | 40 | yarn run build 41 | 42 | -------------------------------------------------------------------------------- /vige-api/vige/migrations/versions/16e4955fffad_media_model.py: -------------------------------------------------------------------------------- 1 | """media model 2 | 3 | Revision ID: 16e4955fffad 4 | Revises: 1804876c9b60 5 | Create Date: 2018-08-29 17:37:18.838203 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | from sqlalchemy.dialects import postgresql 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = '16e4955fffad' 14 | down_revision = '1804876c9b60' 15 | branch_labels = None 16 | depends_on = None 17 | 18 | 19 | def upgrade(): 20 | # ### commands auto generated by Alembic - please adjust! ### 21 | op.create_table('media', 22 | sa.Column('id', sa.BigInteger(), nullable=False), 23 | sa.Column('profile', postgresql.JSONB(astext_type=sa.Text()), server_default='{}', nullable=False), 24 | sa.Column('object_type', sa.Unicode(), nullable=True), 25 | sa.Column('object_id', sa.BigInteger(), nullable=True), 26 | sa.PrimaryKeyConstraint('id') 27 | ) 28 | # ### end Alembic commands ### 29 | 30 | 31 | def downgrade(): 32 | # ### commands auto generated by Alembic - please adjust! ### 33 | op.drop_table('media') 34 | # ### end Alembic commands ### 35 | -------------------------------------------------------------------------------- /vige-bo/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 iView 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vige-wechat/vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | 4 | const env = process.env.NODE_ENV || 'development' 5 | fs.writeFileSync(path.join(__dirname, './config/env.js'), `export default '${env}' 6 | `) 7 | 8 | // 项目部署基础 9 | // 默认情况下,我们假设你的应用将被部署在域的根目录下, 10 | // 例如:https://www.my-app.com/ 11 | // 默认:'/' 12 | // 如果您的应用程序部署在子路径中,则需要在这指定子路径 13 | // 例如:https://www.foobar.com/my-app/ 14 | // 需要将它改为'/my-app/' 15 | const BASE_URL = '/' 16 | 17 | module.exports = { 18 | // Project deployment base 19 | // By default we assume your app will be deployed at the root of a domain, 20 | // e.g. https://www.my-app.com/ 21 | // If your app is deployed at a sub-path, you will need to specify that 22 | // sub-path here. For example, if your app is deployed at 23 | // https://www.foobar.com/my-app/ 24 | // then change this to '/my-app/' 25 | baseUrl: BASE_URL, 26 | // 打包时不生成.map文件 27 | productionSourceMap: false, 28 | devServer: { 29 | disableHostCheck: true, 30 | proxy: { 31 | '/v1': { 32 | target: 'http://localhost:5000', 33 | changeOrigin: true 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Vige Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /vige-wechat/src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Home from './views/home.vue' 4 | import Auth from './views/auth' 5 | import BindBoUser from './views/bind-bo-user' 6 | import BoWorkbench from './views/bo-workbench' 7 | 8 | Vue.use(Router) 9 | 10 | export default new Router({ 11 | routes: [ 12 | { 13 | path: '/', 14 | name: 'home', 15 | meta: { 16 | title: 'Wechat title', 17 | needAuth: true 18 | }, 19 | component: Home 20 | }, 21 | { 22 | path: '/wechat-auth', 23 | name: 'wechat-auth', 24 | meta: { 25 | title: 'Wechat title', 26 | needAuth: false 27 | }, 28 | component: Auth 29 | }, 30 | { 31 | path: '/bind-bo-user', 32 | name: 'bind-bo-user', 33 | meta: { 34 | title: 'Wechat title', 35 | needAuth: true 36 | }, 37 | component: BindBoUser 38 | }, 39 | { 40 | path: '/workbench', 41 | name: 'workbench', 42 | meta: { 43 | title: 'Wechat title', 44 | needAuth: true 45 | }, 46 | component: BoWorkbench 47 | } 48 | ] 49 | }) 50 | -------------------------------------------------------------------------------- /vige-web/src/components/preview/image-preview.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 31 | 32 | -------------------------------------------------------------------------------- /vige-bo/src/components/charts/bar.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 44 | 45 | 50 | -------------------------------------------------------------------------------- /vige-api/vige/api/media/api.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from fastapi import UploadFile, File 3 | from ...db import sm 4 | from .. import router as app 5 | from ..jwt import login_required, get_user 6 | from ..utils import abort_json 7 | from .forms import ImageForm, AudioForm, ALLOWED_IMAGE_EXTENSIONS 8 | from .models import MediaModel 9 | from ...config import config 10 | 11 | 12 | @app.post('/media') 13 | @login_required 14 | async def upload_image(image: UploadFile = File(...)): 15 | ext = image.filename.split('.')[-1] 16 | if ext not in ALLOWED_IMAGE_EXTENSIONS: 17 | raise abort_json(400, '图片格式错误') 18 | key = uuid.uuid4().hex 19 | secure_filename = f"image_{key}.{ext}" 20 | filepath = config.UPLOADS_DEFAULT_DEST 21 | # 保存文件 22 | with open(f"{filepath}/{secure_filename}", "wb") as buffer: 23 | buffer.write(await image.read()) 24 | 25 | with sm.transaction_scope() as sa: 26 | m = MediaModel.create(sa, profile={}) 27 | m.filename = secure_filename 28 | m.save_thumbnail(secure_filename) 29 | sa.commit() 30 | data = m.dump() 31 | return dict( 32 | success=True, 33 | data=data, 34 | ) 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /vige-api/vige/translations/zh/LC_MESSAGES/messages.po: -------------------------------------------------------------------------------- 1 | # Chinese translations for PROJECT. 2 | # Copyright (C) 2018 ORGANIZATION 3 | # This file is distributed under the same license as the PROJECT project. 4 | # FIRST AUTHOR , 2018. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: PROJECT VERSION\n" 9 | "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" 10 | "POT-Creation-Date: 2018-10-10 17:23+0800\n" 11 | "PO-Revision-Date: 2018-10-10 15:47+0800\n" 12 | "Last-Translator: FULL NAME \n" 13 | "Language: zh\n" 14 | "Language-Team: zh \n" 15 | "Plural-Forms: nplurals=1; plural=0\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=utf-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Generated-By: Babel 2.6.0\n" 20 | 21 | msgid "Sensitive Info View" 22 | msgstr "敏感用户信息 - 查看" 23 | 24 | msgid "Settings Manage" 25 | msgstr "管理系统配置" 26 | 27 | msgid "Roles View" 28 | msgstr "查看角色" 29 | 30 | msgid "Roles Manage" 31 | msgstr "管理角色" 32 | 33 | msgid "Users View" 34 | msgstr "查看用户列表" 35 | 36 | msgid "Users Manage" 37 | msgstr "管理用户" 38 | 39 | msgid "Users WeChat Bind Manage" 40 | msgstr "管理微信账户绑定" 41 | 42 | msgid "System" 43 | msgstr "系统级别" 44 | 45 | msgid "User" 46 | msgstr "后台用户" 47 | -------------------------------------------------------------------------------- /vige-bo/src/view/main/components/language/language.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 52 | -------------------------------------------------------------------------------- /vige-web/src/views/main/components/language/language.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 52 | -------------------------------------------------------------------------------- /vige-api/vige/api/errors.py: -------------------------------------------------------------------------------- 1 | from fastapi import HTTPException, Request 2 | from fastapi.responses import JSONResponse 3 | from .wechat import wechat 4 | 5 | 6 | async def handle_http_error(request: Request, exc: Exception): 7 | is_internal_api = '/v1/internal' in str(request.url) 8 | extra = {} 9 | if isinstance(exc, HTTPException): 10 | if exc.status_code == 400: 11 | desc = exc.detail 12 | elif exc.status_code == 401: 13 | if is_internal_api: 14 | desc = '请提供 api key' 15 | else: 16 | desc = '无法获取帐号信息,
请重新打开服务' 17 | extra['redirect_url'] = wechat.oauth_url 18 | elif exc.status_code == 403: 19 | desc = '您无权进行该操作' 20 | elif exc.status_code == 404: 21 | desc = '找不到该内容' 22 | elif exc.status_code == 405: 23 | desc = '请求方法不支持' 24 | else: 25 | desc = '服务出错,请稍后再试' 26 | status_code = exc.status_code 27 | else: 28 | desc = '服务出错,请稍后再试' 29 | status_code = 500 30 | return JSONResponse( 31 | status_code=status_code, 32 | content={ 33 | "success": False, 34 | "error": {"message": desc}, 35 | **extra, 36 | } 37 | ) -------------------------------------------------------------------------------- /vige-bo/src/view/main/components/header-bar/custom-bread-crumb/custom-bread-crumb.vue: -------------------------------------------------------------------------------- 1 | 11 | 47 | -------------------------------------------------------------------------------- /vige-bo/src/view/main/components/side-menu/side-menu-item.vue: -------------------------------------------------------------------------------- 1 | 19 | 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | # Backend 4 | API_DIR := vige-api 5 | API_ENV := $(API_DIR)/vige/local_config.env 6 | 7 | .PHONY: api dev worker migrate lint test 8 | 9 | setup-api: 10 | cd $(API_DIR) && pipenv sync --dev 11 | 12 | env-api: 13 | @[ -f $(API_ENV) ] || (echo "Creating $(API_ENV) from template" && \ 14 | cat > $(API_ENV) << 'EOF'\ 15 | ENV=local\ 16 | DEBUG=true\ 17 | SECRET_KEY=dev_secret\ 18 | SQLALCHEMY_DATABASE_URI=postgresql://postgres:postgres@localhost:5432/vige\ 19 | REDIS_HOST=localhost\ 20 | REDIS_PORT=6379\ 21 | AUTHJWT_SECRET_KEY=dev_jwt\ 22 | EXTERNAL_URL=http://localhost:8000\ 23 | EOF 24 | ) 25 | 26 | api: env-api 27 | cd $(API_DIR) && pipenv run uvicorn vige.app:app --reload --port 8000 28 | 29 | worker: 30 | cd $(API_DIR) && pipenv run huey_consumer -w 2 vige.huey_app.huey 31 | 32 | migrate: 33 | cd $(API_DIR) && pipenv run alembic upgrade head 34 | 35 | lint: 36 | cd $(API_DIR) && pipenv run python -m pip install ruff black isort || true && \ 37 | ruff check vige || true && black --check vige || true && isort --check-only vige || true 38 | 39 | test: 40 | cd $(API_DIR) && pipenv run pytest -q || true 41 | 42 | # Frontends 43 | web: 44 | cd vige-web && yarn install && yarn dev 45 | 46 | bo: 47 | cd vige-bo && yarn install && yarn dev 48 | 49 | wechat: 50 | cd vige-wechat && yarn install && yarn dev 51 | 52 | -------------------------------------------------------------------------------- /vige-web/src/views/main/components/header-bar/custom-bread-crumb/custom-bread-crumb.vue: -------------------------------------------------------------------------------- 1 | 14 | 50 | -------------------------------------------------------------------------------- /vige-bo/src/styles/common.less: -------------------------------------------------------------------------------- 1 | .margin-top-8 { 2 | margin-top: 8px; 3 | } 4 | 5 | .margin-top-10 { 6 | margin-top: 10px; 7 | } 8 | 9 | .margin-top-20 { 10 | margin-top: 20px; 11 | } 12 | 13 | .margin-left-10 { 14 | margin-left: 10px; 15 | } 16 | 17 | .margin-bottom-10 { 18 | margin-bottom: 10px; 19 | } 20 | 21 | .margin-bottom-100 { 22 | margin-bottom: 100px; 23 | } 24 | 25 | .margin-right-10 { 26 | margin-right: 10px; 27 | } 28 | 29 | .padding-left-6 { 30 | padding-left: 6px; 31 | } 32 | 33 | .padding-left-8 { 34 | padding-left: 5px; 35 | } 36 | 37 | .padding-left-10 { 38 | padding-left: 10px; 39 | } 40 | 41 | .padding-left-20 { 42 | padding-left: 20px; 43 | } 44 | 45 | .width-250px { 46 | width: 250px; 47 | } 48 | 49 | .height-100 { 50 | height: 100%; 51 | } 52 | 53 | .height-120px { 54 | height: 100px; 55 | } 56 | 57 | .height-200px { 58 | height: 200px; 59 | } 60 | 61 | .height-492px { 62 | height: 492px; 63 | } 64 | 65 | .height-460px { 66 | height: 460px; 67 | } 68 | 69 | .line-gray { 70 | height: 0; 71 | border-bottom: 2px solid #dcdcdc; 72 | } 73 | 74 | .notwrap { 75 | word-break: keep-all; 76 | white-space: nowrap; 77 | overflow: hidden; 78 | text-overflow: ellipsis; 79 | } 80 | 81 | .padding-left-5 { 82 | padding-left: 10px; 83 | } 84 | 85 | [v-cloak] { 86 | display: none; 87 | } 88 | 89 | .pull-right { 90 | float: right; 91 | } 92 | 93 | .pull-left { 94 | float: left; 95 | } 96 | -------------------------------------------------------------------------------- /vige-api/vige/api/settings/api.py: -------------------------------------------------------------------------------- 1 | from fastapi import Depends, Request, Response 2 | from sqlalchemy.orm import Session 3 | from ...db import sm 4 | from .. import router as app 5 | from ..bo_user.security import perm_accepted 6 | from ..constants import BoPermission 7 | from ..decorators import validates 8 | from ..utils import get_settings 9 | from .fields import BaseField 10 | from .forms import ConfigForm 11 | from .settings import Settings as Configs 12 | 13 | 14 | @app.get('/admin/configs') 15 | def list_configs(settings: Configs = Depends(get_settings), 16 | db: Session = Depends(sm.get_db)): 17 | cls = Configs 18 | fields = [x for x in vars(cls).keys() if not x.startswith('__')] 19 | ret = [] 20 | for field_key in fields: 21 | field = getattr(cls, field_key, None) 22 | if not isinstance(field, BaseField): 23 | continue 24 | ret.append(dict( 25 | key=field_key, 26 | value=getattr(settings, field_key, None), 27 | name=field.name, 28 | desc=field.desc, 29 | type=field.type, 30 | )) 31 | return dict(success=True, configs=ret) 32 | 33 | 34 | @app.put('/admin/configs') 35 | @perm_accepted(BoPermission.live_settings_manage) 36 | def create_config(form: ConfigForm, settings: Configs = Depends(get_settings)): 37 | with sm.transaction_scope() as sa: 38 | setattr(settings, form.key, form.value) 39 | return dict(success=True) 40 | 41 | -------------------------------------------------------------------------------- /vige-api/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://mirrors.aliyun.com/pypi/simple/" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | uvicorn = "==0.34.0" 8 | sqlalchemy = "==2.0.8" 9 | databases = "==0.9.0" 10 | pydantic = "==2.10.3" 11 | alembic = "==1.14.0" 12 | psycopg2 = "==2.9.5" 13 | requests = "==2.32.2" 14 | bcrypt = "==4.2.1" 15 | huey = "==2.5.0" 16 | redis = "==2.10.6" 17 | cryptography = ">=3.3" 18 | pyOpenSSL = ">=19.0.0" 19 | SQLAlchemy-Utils = "==0.41.2" 20 | jinja2 = "3.1.4" 21 | itsdangerous = "==2.0.1" 22 | aliyun-python-sdk-core-v3 = "*" 23 | lxml = "*" 24 | tiktoken = "==0.5.1" 25 | pillow = "==10.3.0" 26 | pydantic-settings = "==2.7.0" 27 | fastapi-babel = "==1.0.0" 28 | factory-boy = "==2.10.0" 29 | fastapi-cache = "==0.1.0" 30 | pytz = "==2024.2" 31 | fastapi = {extras = ["all"], version = "==0.115.6"} 32 | python-mimeparse = "==2.0.0" 33 | websockets = "==14.1" 34 | pydub = "==0.25.1" 35 | http-client = "==0.1.22" 36 | httpx = "==0.28.1" 37 | websockets-proxy = "==0.1.3" 38 | numpy = "==1.26.4" 39 | soundfile = "==0.12.1" 40 | httpx-socks = "==0.10.0" 41 | openai = "*" 42 | zhipuai = "==2.1.5.20250106" 43 | async-fastapi-jwt-auth = "==0.6.6" 44 | python-docx = "*" 45 | pymupdf = "*" 46 | docxtpl = "*" 47 | pandas = "*" 48 | xlrd = "*" 49 | wechatpayv3 = "*" 50 | pycryptodome = "*" 51 | 52 | [dev-packages] 53 | pytest = "*" 54 | pytest-cov = "*" 55 | pytest-watch = "*" 56 | swagger-spec-validator = "*" 57 | requests-mock = "*" 58 | 59 | [requires] 60 | python_version = "3.10" 61 | -------------------------------------------------------------------------------- /vige-bo/src/components/tables/qr-code-col.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 56 | 57 | -------------------------------------------------------------------------------- /vige-bo/src/store/module/app.js: -------------------------------------------------------------------------------- 1 | import { getBreadCrumbList, getMenuByRouter, getHomeRoute } from '@/libs/util' 2 | import routers from '@/router/routers' 3 | import {startsWith } from 'lodash' 4 | 5 | export default { 6 | state: { 7 | breadCrumbList: [], 8 | homeRoute: getHomeRoute(routers), 9 | local: '', 10 | configs: {}, 11 | listQuery: { 12 | // 列表页的过滤、搜索、排序参数 13 | // pageKey: {} 14 | } 15 | }, 16 | getters: { 17 | menuList: (state, getters, rootState) => getMenuByRouter(routers, rootState.user.user.permissions), 18 | listQuery: state => pageKey => { 19 | // filter/search/sort parameters of a table page 20 | return state.listQuery[pageKey] 21 | } 22 | }, 23 | actions: { 24 | async fetchConfig ({commit}, {vue}) { 25 | let resp = await vue.$http.get('/admin/configs') 26 | commit('setConfigs', resp.configs) 27 | } 28 | }, 29 | mutations: { 30 | setBreadCrumb (state, routeMetched) { 31 | state.breadCrumbList = getBreadCrumbList(routeMetched, state.homeRoute) 32 | }, 33 | 34 | setConfigs ( state, configs ) { 35 | state.configs = configs 36 | }, 37 | 38 | clearPageQueryCache (state, page) { 39 | Object.keys(state.listQuery).forEach(key => { 40 | if (startsWith(key, page)) { 41 | delete state.listQuery[key] 42 | } 43 | }) 44 | }, 45 | 46 | setListQuery(state, {pageKey, params}) { 47 | state.listQuery[pageKey] = params 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /vige-web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "orangegpt", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@websanova/vue-auth": "^2.21.14-beta", 12 | "@xkeshi/vue-qrcode": "^1.0.0", 13 | "axios": "^0.18.0", 14 | "babel-preset-es2015": "^6.24.1", 15 | "compressorjs": "^1.0.6", 16 | "eslint-plugin-html": "^4.0.5", 17 | "highlight.js": "10.7.2", 18 | "iview": "^3.0.0", 19 | "iview-area": "^1.5.17", 20 | "js-cookie": "^2.2.0", 21 | "markdown-it-table": "^2.0.4", 22 | "marked": "1.2.7", 23 | "moment": "^2.22.2", 24 | "vue": "^2.5.10", 25 | "vue-axios": "^2.1.3", 26 | "vue-i18n": "^7.8.0", 27 | "vue-router": "3.0.1", 28 | "vuex": "^3.0.1" 29 | }, 30 | "devDependencies": { 31 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 32 | "@vue/cli-plugin-babel": "^3.0.0-beta.10", 33 | "@vue/cli-plugin-eslint": "^3.0.0-beta.10", 34 | "@vue/cli-plugin-unit-mocha": "^3.0.0-beta.10", 35 | "@vue/cli-service": "^3.0.0-beta.10", 36 | "@vue/eslint-config-standard": "^3.0.0-beta.10", 37 | "babel-polyfill": "^6.26.0", 38 | "chai": "^4.1.2", 39 | "compression-webpack-plugin": "^6.0.5", 40 | "eslint-plugin-cypress": "^2.0.1", 41 | "less": "^2.7.3", 42 | "less-loader": "^4.0.5", 43 | "lint-staged": "^6.0.0", 44 | "vue-template-compiler": "^2.5.13" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /vige-bo/src/components/charts/pie.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 56 | 57 | 62 | -------------------------------------------------------------------------------- /vige-wechat/src/libs/api.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { Indicator } from 'mint-ui' 3 | import Cookies from 'js-cookie' 4 | import store from '../store' 5 | 6 | export const api = axios.create({ 7 | baseURL: '/v1' 8 | }) 9 | 10 | const getCookie = (name) => { 11 | return Cookies.get(name) || null 12 | } 13 | 14 | api.interceptors.request.use(config => { 15 | config.params = Object.assign(config.params || {}, {nonce: Date.now()}) 16 | let csrfCookie = getCookie('vige_auth_csrf_cookie') 17 | if (csrfCookie) { 18 | config.headers = { 19 | 'X-CSRF-TOKEN': csrfCookie 20 | } 21 | } 22 | store.commit('setLoading', true) 23 | Indicator.open({ 24 | text: '加载中...', 25 | spinnerType: 'fading-circle' 26 | }) 27 | return config 28 | }) 29 | api.interceptors.response.use(response => { 30 | store.commit('setLoading', false) 31 | Indicator.close() 32 | if (!response.data.success) { 33 | return Promise.reject(new Error(response.data.error.message)) 34 | } 35 | return response 36 | }, error => { 37 | store.commit('setLoading', false) 38 | Indicator.close() 39 | if (error.response.status === 401) { 40 | if (error.response.data.redirect_url) { 41 | let from = window.localStorage.getItem('wechat_from') 42 | if (!from) { 43 | window.localStorage.setItem('wechat_from', window.location.href) 44 | } 45 | window.location.href = error.response.data.redirect_url 46 | } 47 | } 48 | try { 49 | return Promise.reject(error.response.data) 50 | } catch (e) { 51 | return Promise.reject(error) 52 | } 53 | }) 54 | -------------------------------------------------------------------------------- /vige-bo/vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | 4 | const resolve = dir => { 5 | return path.join(__dirname, dir) 6 | } 7 | 8 | const env = process.env.NODE_ENV || 'development' 9 | fs.writeFileSync(path.join(__dirname, './config/env.js'), `export default '${env}' 10 | `) 11 | 12 | // 项目部署基础 13 | // 默认情况下,我们假设你的应用将被部署在域的根目录下, 14 | // 例如:https://www.my-app.com/ 15 | // 默认:'/' 16 | // 如果您的应用程序部署在子路径中,则需要在这指定子路径 17 | // 例如:https://www.foobar.com/my-app/ 18 | // 需要将它改为'/my-app/' 19 | const BASE_URL = './' 20 | 21 | module.exports = { 22 | // Project deployment base 23 | // By default we assume your app will be deployed at the root of a domain, 24 | // e.g. https://www.my-app.com/ 25 | // If your app is deployed at a sub-path, you will need to specify that 26 | // sub-path here. For example, if your app is deployed at 27 | // https://www.foobar.com/my-app/ 28 | // then change this to '/my-app/' 29 | baseUrl: BASE_URL, 30 | // tweak internal webpack configuration. 31 | // see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md 32 | chainWebpack: config => { 33 | config.resolve.alias 34 | .set('@', resolve('src')) // key,value自行定义,比如.set('@@', resolve('src/components')) 35 | .set('_c', resolve('src/components')) 36 | .set('_conf', resolve('config')) 37 | }, 38 | // 打包时不生成.map文件 39 | productionSourceMap: false, 40 | devServer: { 41 | disableHostCheck: true, 42 | proxy: { 43 | '/v1': { 44 | target: 'http://localhost:8000', 45 | changeOrigin: true 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /vige-bo/src/view/main/main.less: -------------------------------------------------------------------------------- 1 | .main{ 2 | .logo-con { 3 | padding: 15px; 4 | text-align: center; 5 | cursor: pointer; 6 | 7 | img { 8 | height: 32px; 9 | width: auto; 10 | } 11 | } 12 | .header-con{ 13 | background: #fff; 14 | padding: 0 20px; 15 | position: 'fixed'; 16 | width: '100%'; 17 | } 18 | .main-layout-con{ 19 | height: 100%; 20 | overflow: hidden; 21 | } 22 | .main-content-con{ 23 | height: ~"calc(100% - 60px)"; 24 | overflow: hidden; 25 | } 26 | .tag-nav-wrapper{ 27 | padding: 0; 28 | height:40px; 29 | background:#F0F0F0; 30 | overflow: hidden; 31 | } 32 | .content-wrapper{ 33 | padding: 10px 10px 0 10px; 34 | height: ~"calc(100% - 80px)"; 35 | overflow: auto; 36 | } 37 | .left-sider{ 38 | .ivu-layout-sider-children{ 39 | overflow-y: scroll; 40 | } 41 | } 42 | } 43 | .ivu-menu-item > i{ 44 | margin-right: 12px !important; 45 | } 46 | .ivu-menu-submenu > .ivu-menu > .ivu-menu-item > i { 47 | margin-right: 8px !important; 48 | } 49 | .ivu-select-dropdown.ivu-dropdown-transfer{ 50 | max-height: 1000px; 51 | & div.ivu-dropdown{ 52 | width: 100%; 53 | margin: 0; 54 | line-height: normal; 55 | padding: 7px 0 6px 16px; 56 | clear: both; 57 | font-size: 12px !important; 58 | white-space: nowrap; 59 | list-style: none; 60 | cursor: pointer; 61 | transition: background 0.2s ease-in-out; 62 | &:hover{ 63 | background: rgba(100, 100, 100, 0.1); 64 | } 65 | & * { 66 | color: #515a6e; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /vige-bo/src/components/tables/search-input.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 24 | 25 | 65 | 66 | -------------------------------------------------------------------------------- /vige-web/src/views/main/main.less: -------------------------------------------------------------------------------- 1 | .main{ 2 | .logo-con { 3 | padding: 15px; 4 | text-align: center; 5 | cursor: pointer; 6 | 7 | img { 8 | height: 32px; 9 | width: auto; 10 | } 11 | } 12 | .header-con{ 13 | background: transparent; 14 | padding: 0 20px; 15 | position: 'fixed'; 16 | width: '100%'; 17 | } 18 | .main-layout-con{ 19 | height: 100%; 20 | overflow: hidden; 21 | } 22 | .main-content-con{ 23 | height: ~"calc(100vh - 60px)"; 24 | overflow: hidden; 25 | } 26 | .tag-nav-wrapper{ 27 | padding: 0; 28 | height:40px; 29 | background:#F0F0F0; 30 | overflow: hidden; 31 | } 32 | .content-wrapper{ 33 | padding: 10px 10px 0 10px; 34 | height: ~"calc(100vh - 80px)"; 35 | overflow: auto; 36 | } 37 | .left-sider{ 38 | .ivu-layout-sider-children{ 39 | overflow-y: scroll; 40 | } 41 | } 42 | } 43 | .ivu-menu-item > i{ 44 | margin-right: 12px !important; 45 | } 46 | .ivu-menu-submenu > .ivu-menu > .ivu-menu-item > i { 47 | margin-right: 8px !important; 48 | } 49 | .ivu-select-dropdown.ivu-dropdown-transfer{ 50 | max-height: 1000px; 51 | & div.ivu-dropdown{ 52 | width: 100%; 53 | margin: 0; 54 | line-height: normal; 55 | padding: 7px 0 6px 16px; 56 | clear: both; 57 | font-size: 12px !important; 58 | white-space: nowrap; 59 | list-style: none; 60 | cursor: pointer; 61 | transition: background 0.2s ease-in-out; 62 | &:hover{ 63 | background: rgba(100, 100, 100, 0.1); 64 | } 65 | & * { 66 | color: #515a6e; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /vige-api/vige/api/decorators.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | from fastapi import HTTPException, Request 3 | from pydantic import BaseModel, ValidationError 4 | 5 | 6 | # 验证POST/PUT请求体 7 | def validates(form_cls: BaseModel, form_name: str, obj_id_name=None, form_data=None): 8 | def decorator(fn): 9 | async def wrapper(*args, request: Request, **kwargs): 10 | request_data = form_data or await request.json() 11 | data = deepcopy(request_data or {}) 12 | data.update({'id': kwargs.get(obj_id_name, None)}) 13 | try: 14 | form = form_cls(**data) 15 | form.id = kwargs.get(obj_id_name, None) 16 | except ValidationError as e: 17 | message = f"{form_name} validation failed" 18 | errors = e.errors() 19 | raise HTTPException(status_code=400, detail={"message": message, "errors": errors}) 20 | 21 | return await fn(*args, form=form, **kwargs) 22 | return wrapper 23 | return decorator 24 | 25 | 26 | # 验证GET查询参数 27 | def validates_args(form_cls: BaseModel, form_name: str): 28 | def decorator(fn): 29 | async def wrapper(*args, request: Request, **kwargs): 30 | try: 31 | form = form_cls(**request.query_params) 32 | except ValidationError as e: 33 | message = f"Fetching {form_name} failed" 34 | errors = e.errors() 35 | raise HTTPException(status_code=400, detail={"message": message, "errors": errors}) 36 | 37 | return await fn(*args, form=form, **kwargs) 38 | return wrapper 39 | return decorator 40 | 41 | 42 | -------------------------------------------------------------------------------- /vige-api/vige/api/bo_user/verify_code.py: -------------------------------------------------------------------------------- 1 | import random 2 | from redis import Redis 3 | from fastapi import Depends, HTTPException 4 | from ..utils import settings 5 | from vige.config import config 6 | from vige.app_factory import get_redis_client 7 | 8 | KEY_TPL = 'code-verify:{}' 9 | 10 | 11 | # 验证码管理类 12 | class CodeVerification: 13 | 14 | def __init__(self, redis: Redis): 15 | self.redis = redis 16 | self.ttl = config.VERIFY_CODE_TTL 17 | 18 | def _get(self, key): 19 | v = self.redis.get(KEY_TPL.format(key)) 20 | if v: 21 | return v.decode('utf-8') 22 | 23 | def _set(self, key, value, ex): 24 | return self.redis.set(KEY_TPL.format(key), value, ex) 25 | 26 | def _del(self, key): 27 | return self.redis.delete(KEY_TPL.format(key)) 28 | 29 | def _gen_code(self): 30 | return '{:06d}'.format(random.randint(0, 999999)) 31 | 32 | def get_or_create(self, key): 33 | code = self._get(key) 34 | if not code: 35 | code = self._gen_code() 36 | self._set(key, code, self.ttl) 37 | return code 38 | 39 | def is_code_valid(self, key): 40 | return True if self._get(key) else False 41 | 42 | def verify(self, key, code, remove_if_match=False): 43 | if settings.enable_two_fa_debug_mode and code == '888888': 44 | return True 45 | print(self._get(key), code) 46 | matched = self._get(key) == code if code else False 47 | if remove_if_match and matched: 48 | self._del(key) 49 | return matched 50 | 51 | 52 | redis_client = get_redis_client() 53 | code_verification = CodeVerification(redis_client) 54 | -------------------------------------------------------------------------------- /vige-web/src/libs/filters.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | 3 | let filters = {} 4 | 5 | export default filters 6 | 7 | let formatMoney = (value) => { 8 | if (value === null) { 9 | return 'N/A' 10 | } 11 | if (isNaN(value * 1)) { 12 | return '' 13 | } 14 | let negativeFlag = value < 0 ? '-' : '' 15 | value = Math.abs(value) 16 | if (value === Math.round(value)) { 17 | value = value.toFixed(0) 18 | } else { 19 | value = value.toFixed(2) 20 | } 21 | value = parseFloat(value) 22 | return `${negativeFlag}¥${value.toLocaleString('en-US')}` 23 | } 24 | 25 | let formatEmpty = (value) => { 26 | return value || '-' 27 | } 28 | 29 | let formatDatetimeUTC = (value, fmt = 'YYYY-MM-DD') => { 30 | return formatDatetime(value, fmt, 'utc', 'local') 31 | } 32 | 33 | let formatDatetime = (value, fmt = 'YYYY-MM-DD HH:mm:ss', from = 'local', to = 'utc') => { 34 | if (!value) { 35 | return '' 36 | } 37 | if (!fmt) { 38 | fmt = 'YYYY-MM-DD HH:mm:ss' 39 | } 40 | let dt = moment(value) 41 | if (from !== to) { 42 | if (from === 'utc') { 43 | dt = moment(moment.utc(value).toDate()).local() 44 | } 45 | if (to === 'utc') { 46 | dt = moment.utc(value) 47 | } 48 | } 49 | return dt.format(fmt) 50 | } 51 | 52 | let formatClientInfo = (info = []) => { 53 | return info.filter(item => !!item).join(', ') 54 | } 55 | 56 | let formatNull = (value) => { 57 | if (value === null || value === undefined || value === '') { 58 | return '-' 59 | } else { 60 | return value 61 | } 62 | } 63 | 64 | export { 65 | formatDatetime, 66 | formatDatetimeUTC, 67 | formatMoney, 68 | formatClientInfo, 69 | formatEmpty, 70 | formatNull, 71 | } 72 | -------------------------------------------------------------------------------- /vige-bo/src/libs/filters.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | 3 | let filters = {} 4 | 5 | export default filters 6 | 7 | let formatMoney = (value) => { 8 | if (value === null) { 9 | return 'N/A' 10 | } 11 | if (isNaN(value * 1)) { 12 | return '' 13 | } 14 | let negativeFlag = value < 0 ? '-' : '' 15 | value = Math.abs(value) 16 | if (value === Math.round(value)) { 17 | value = value.toFixed(0) 18 | } else { 19 | value = value.toFixed(2) 20 | } 21 | value = parseFloat(value) 22 | return `${negativeFlag}¥${value.toLocaleString('en-US')}` 23 | } 24 | 25 | let formatEmpty = (value) => { 26 | return value || '-' 27 | } 28 | 29 | let formatDatetimeUTC = (value, fmt = 'YYYY-MM-DD HH:mm:ss') => { 30 | return formatDatetime(value, fmt, 'utc', 'local') 31 | } 32 | 33 | let formatDatetime = (value, fmt = 'YYYY-MM-DD HH:mm:ss', from = 'local', to = 'utc') => { 34 | if (!value) { 35 | return '' 36 | } 37 | if (!fmt) { 38 | fmt = 'YYYY-MM-DD HH:mm:ss' 39 | } 40 | let dt = moment(value) 41 | if (from !== to) { 42 | if (from === 'utc') { 43 | dt = moment(moment.utc(value).toDate()).local() 44 | } 45 | if (to === 'utc') { 46 | dt = moment.utc(value) 47 | } 48 | } 49 | return dt.format(fmt) 50 | } 51 | 52 | let formatClientInfo = (info = []) => { 53 | return info.filter(item => !!item).join(', ') 54 | } 55 | 56 | let formatNull = (value) => { 57 | if (value === null || value === undefined || value === '') { 58 | return '-' 59 | } else { 60 | return value 61 | } 62 | } 63 | 64 | export { 65 | formatDatetime, 66 | formatDatetimeUTC, 67 | formatMoney, 68 | formatClientInfo, 69 | formatEmpty, 70 | formatNull, 71 | } 72 | -------------------------------------------------------------------------------- /vige-api/vige/api/wechat/wechat_pay_base.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | __all__ = ("Map", "WechatError") 5 | 6 | 7 | basestring = (str, bytes) 8 | 9 | 10 | class WechatError(Exception): 11 | 12 | def __init__(self, msg): 13 | super(WechatError, self).__init__(msg) 14 | 15 | 16 | class Map(dict): 17 | """ 18 | 提供字典的dot访问模式 19 | Example: 20 | m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer']) 21 | """ 22 | def __init__(self, *args, **kwargs): 23 | super(Map, self).__init__(*args, **kwargs) 24 | for arg in args: 25 | if isinstance(arg, dict): 26 | for k, v in arg.items(): 27 | if isinstance(v, dict): 28 | v = Map(v) 29 | self[k] = v 30 | 31 | if kwargs: 32 | for k, v in kwargs.items(): 33 | if isinstance(v, dict): 34 | v = Map(v) 35 | self[k] = v 36 | 37 | def __getattr__(self, attr): 38 | return self[attr] 39 | 40 | def __setattr__(self, key, value): 41 | self.__setitem__(key, value) 42 | 43 | def __getitem__(self, key): 44 | if key not in self.__dict__: 45 | super(Map, self).__setitem__(key, {}) 46 | self.__dict__.update({key: Map()}) 47 | return self.__dict__[key] 48 | 49 | def __setitem__(self, key, value): 50 | super(Map, self).__setitem__(key, value) 51 | self.__dict__.update({key: value}) 52 | 53 | def __delattr__(self, item): 54 | self.__delitem__(item) 55 | 56 | def __delitem__(self, key): 57 | super(Map, self).__delitem__(key) 58 | del self.__dict__[key] -------------------------------------------------------------------------------- /vige-api/vige/api/users/forms.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from fastapi import Depends 3 | from pydantic import BaseModel, field_validator, ValidationError 4 | from sqlalchemy.orm import Session 5 | from async_fastapi_jwt_auth import AuthJWT 6 | from async_fastapi_jwt_auth.exceptions import AuthJWTException 7 | from ...app_factory import auth_dep 8 | from ..bo_user.models import BoUser 9 | from ..utils import config 10 | from ...db import sm 11 | from ..forms import BaseFilterForm 12 | 13 | 14 | class UserFilterForm(BaseFilterForm): 15 | keyword: Optional[str] = None 16 | 17 | 18 | class UserSendCodeForm(BaseModel): 19 | mobile: str 20 | 21 | @field_validator('mobile') 22 | def validate_mobile(cls, v, values): 23 | if not v: 24 | raise ValueError('手机号为必填项') 25 | # 验证手机号 26 | if not v.isdigit(): 27 | raise ValueError('手机号格式错误') 28 | if len(v) != 11: 29 | raise ValueError('手机号格式错误') 30 | return v 31 | 32 | 33 | class UserLoginForm(BaseModel): 34 | mobile: str 35 | code: str 36 | 37 | @field_validator('mobile') 38 | def validate_mobile(cls, v, values): 39 | if not v: 40 | raise ValueError('手机号为必填项') 41 | # 验证手机号 42 | if not v.isdigit(): 43 | raise ValueError('手机号格式错误') 44 | if len(v) != 11: 45 | raise ValueError('手机号格式错误') 46 | return v 47 | 48 | @field_validator('code') 49 | def validate_code(cls, v, values): 50 | if not v: 51 | raise ValueError('验证码为必填项') 52 | if not v.isdigit(): 53 | raise ValueError('验证码格式错误') 54 | if len(v) != 6: 55 | raise ValueError('验证码格式错误') 56 | return v -------------------------------------------------------------------------------- /vige-api/vige/api/constants.py: -------------------------------------------------------------------------------- 1 | from .utils import IntEnum, Enum 2 | 3 | 4 | class BoPermission(Enum): 5 | misc_sensitive_info_view = 'misc_sensitive_info_view' 6 | 7 | # 系统级别 8 | live_settings_manage = 'live_settings_manage' 9 | 10 | # 后台用户 11 | bo_roles_view = 'bo_roles_view' 12 | bo_roles_manage = 'bo_roles_manage' 13 | bo_users_view = 'bo_users_view' 14 | bo_users_manage = 'bo_users_manage' 15 | bo_users_wechat_bind_manage = 'bo_users_wechat_bind_manage' 16 | 17 | 18 | BoPermission.misc_sensitive_info_view.label = 'Sensitive Info View' # '敏感用户信息 - 查看' 19 | BoPermission.live_settings_manage.label = 'Settings Manage' # '管理系统配置' 20 | BoPermission.bo_roles_view.label = 'Roles View' # '查看角色' 21 | BoPermission.bo_roles_manage.label = 'Roles Manage' # '管理角色' 22 | BoPermission.bo_users_view.label = 'Users View' # '查看用户列表' 23 | BoPermission.bo_users_manage.label = 'Users Manage' # '管理用户' 24 | BoPermission.bo_users_wechat_bind_manage.label = 'Users WeChat Bind Manage' # '管理微信账户绑定' 25 | 26 | 27 | # 绑定了后台用户的微信用户牵扯到的权限,请设置 use_in_wechat = True 28 | # BoPermission.wechat_workbench_xxx.use_in_wechat = True 29 | 30 | 31 | class BoPermissionGroup(IntEnum): 32 | system = 100 33 | bo_user = 200 34 | 35 | 36 | BoPermissionGroup.system.label = 'System' # '系统级别' 37 | BoPermissionGroup.bo_user.label = 'User' # '后台用户' 38 | 39 | BoPermissionGroup.system.members = [ 40 | BoPermission.live_settings_manage 41 | ] 42 | 43 | BoPermissionGroup.bo_user.members = [ 44 | BoPermission.misc_sensitive_info_view, 45 | BoPermission.bo_roles_view, 46 | BoPermission.bo_roles_manage, 47 | BoPermission.bo_users_view, 48 | BoPermission.bo_users_manage, 49 | BoPermission.bo_users_wechat_bind_manage, 50 | ] 51 | -------------------------------------------------------------------------------- /vige-api/Makefile: -------------------------------------------------------------------------------- 1 | default: clean test 2 | 3 | install: 4 | PIPENV_VENV_IN_PROJECT=1 pipenv install --dev 5 | 6 | run: 7 | uvicorn vige.app:app --host 192.168.1.5 --port 8000 --reload --log-level debug 8 | 9 | worker: 10 | DEBUG=0 huey_consumer vige.huey_app.huey 11 | 12 | upgrade-db: 13 | alembic upgrade head 14 | 15 | config-db: 16 | alembic -c vige/migrations/alembic.ini current 17 | 18 | db: 19 | alembic revision --autogenerate -m "$(filter-out $@,$(MAKECMDGOALS))" 20 | 21 | lint: 22 | flake8 23 | 24 | test .coverage: 25 | pytest --cov-report=term:skip-covered --cov=. --reset-db . 26 | 27 | htmlcov: .coverage 28 | @coverage html --skip-covered 29 | @echo "open htmlcov/index.html" 30 | 31 | clean: 32 | rm -f .coverage 33 | rm -fr htmlcov 34 | 35 | # ci: clean lint test htmlcov 36 | ci: clean test 37 | 38 | smtp-server: 39 | python -m smtpd -n -c DebuggingServer localhost:10025 40 | 41 | %: 42 | @: 43 | 44 | TRANSLATIONS=vige/translations 45 | DOMAIN=messages 46 | 47 | babel-extract: 48 | pybabel extract -F $(TRANSLATIONS)/messages.cfg --no-location -k lazy_gettext -o $(TRANSLATIONS)/messages.pot vige 49 | 50 | babel-init: babel-extract 51 | pybabel init -D $(DOMAIN) -i $(TRANSLATIONS)/messages.pot -d $(TRANSLATIONS) -l $(LANG) 52 | 53 | babel-update: babel-extract 54 | pybabel update -D $(DOMAIN) -i $(TRANSLATIONS)/messages.pot --no-wrap -d $(TRANSLATIONS) 55 | babel-compile: 56 | pybabel compile -D $(DOMAIN) -d $(TRANSLATIONS) 57 | 58 | # create admin 59 | create-role: 60 | python -m vige.cli bo_create_role -n admin 61 | 62 | create-user: 63 | python -m vige.cli bo_create_user -n admin 64 | 65 | set-role: 66 | python -m vige.cli bo_set_role -u admin -r admin 67 | 68 | set-perm: 69 | python -m vige.cli bo_set_perm -r admin -p all 70 | -------------------------------------------------------------------------------- /vige-web/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueAxios from 'vue-axios' 3 | import App from './App.vue' 4 | import { router } from './router/index' 5 | import store from './store' 6 | import { api } from './libs/api' 7 | import VueRouter from 'vue-router' 8 | import iView from 'iview' 9 | import VueAuth from '@websanova/vue-auth' 10 | import http from '@websanova/vue-auth/drivers/http/axios.1.x.js' 11 | import './styles/theme.less' 12 | import 'iview/dist/styles/iview.css' 13 | import './index.less' 14 | import config from '@/config' 15 | import '@/assets/icons/iconfont.css' 16 | import { getCookie } from './libs/util' 17 | import i18n from '@/locale' 18 | 19 | Vue.use(iView, { 20 | i18n: (key, value) => i18n.t(key, value) 21 | }) 22 | 23 | Vue.config.productionTip = false 24 | Vue.prototype.$config = config 25 | 26 | Vue.use(VueAxios, api) 27 | Vue.use(VueRouter) 28 | Vue.router = router 29 | 30 | http._httpData = (res) => res 31 | 32 | // auth 33 | Vue.use(VueAuth, { 34 | auth: { 35 | request: function (req, token) { 36 | this.options.http._setHeaders.call(this, req, {'X-CSRF-TOKEN': getCookie('vige_auth_csrf_cookie')}) 37 | }, 38 | response: function (res) { 39 | return true 40 | } 41 | }, 42 | http: http, 43 | router: require('@websanova/vue-auth/drivers/router/vue-router.2.x.js'), 44 | loginData: { url: '/web/login', fetchUser: false}, 45 | logoutData: { url: '/web/logout', methods: 'POST', makeRequest: true }, 46 | refreshData: { enabled: false }, 47 | fetchData: { url: '/web/users/me', enabled: true }, 48 | parseUserData: (data) => data.user, 49 | tokenDefaultName: 'vige_auth', 50 | tokenStore: ['cookie'], 51 | }) 52 | 53 | 54 | new Vue({ 55 | router, 56 | i18n, 57 | store, 58 | render: h => h(App) 59 | }).$mount('#app') 60 | -------------------------------------------------------------------------------- /vige-bo/src/router/index.js: -------------------------------------------------------------------------------- 1 | import VueRouter from 'vue-router' 2 | import iView from 'iview' 3 | import { get } from 'lodash' 4 | import routes from './routers' 5 | import store from '../store' 6 | import { setTitle } from '../libs/util' 7 | 8 | export const router = new VueRouter({ 9 | routes, 10 | // mode: 'history' 11 | }) 12 | 13 | const LOGIN_PAGE_NAME = 'login' 14 | 15 | router.beforeEach((to, from, next) => { 16 | iView.LoadingBar.start(); 17 | if (store.getters['user/authorized'] && to.path !== '/') { 18 | // auth is actually checked by vue-auth 19 | // here we only care about permissions 20 | const requiredPerm = get(to, 'meta.requiredPerms') 21 | let ok = false 22 | if (requiredPerm) { 23 | // a route can explicitly state what permission it requires 24 | ok = requiredPerm.filter(p => store.getters['user/hasPermission'](p) === true).length > 0 25 | } else { 26 | // if it doesn't, there is no limit to this path 27 | ok = true 28 | } 29 | if (!ok) { 30 | next({name: 'error_403'}) 31 | } 32 | } 33 | setTitle(to.meta.title) 34 | if (to.name !== LOGIN_PAGE_NAME && !to.query.nonce) { 35 | let nonce = (new Date).getTime() 36 | let query = Object.assign({nonce: nonce}, to.query) 37 | 38 | // See https://github.com/vuejs/vue-router/pull/1906 39 | // 目前在 beforeEach 中无法知晓路由变化是由 push 还是 replace 引发的,这里会 40 | // 导致 replace 失效,如果需要可以暂时在这里 hack 一下,等到 vue-router 问题 41 | // 修复之后,这里可以直接根据调用的是 push 还是 replace 来设置 next 的 replace 42 | // 参数 43 | let replace = false 44 | next({ path: to.path, query: query, replace: replace }) 45 | } else { 46 | next() 47 | } 48 | }) 49 | 50 | router.afterEach(() => { 51 | iView.LoadingBar.finish() 52 | window.scrollTo(0, 0) 53 | }) 54 | 55 | export default router 56 | 57 | -------------------------------------------------------------------------------- /vige-web/src/router/index.js: -------------------------------------------------------------------------------- 1 | import VueRouter from 'vue-router' 2 | import iView from 'iview' 3 | import { get } from 'lodash' 4 | import routes from './routers' 5 | import store from '../store' 6 | import { setTitle } from '../libs/util' 7 | 8 | export const router = new VueRouter({ 9 | routes, 10 | // mode: 'history' 11 | }) 12 | 13 | const LOGIN_PAGE_NAME = 'login' 14 | 15 | router.beforeEach((to, from, next) => { 16 | iView.LoadingBar.start(); 17 | if (store.getters['user/authorized'] && to.path !== '/') { 18 | // auth is actually checked by vue-auth 19 | // here we only care about permissions 20 | const requiredPerm = get(to, 'meta.requiredPerms') 21 | let ok = false 22 | if (requiredPerm) { 23 | // a route can explicitly state what permission it requires 24 | ok = requiredPerm.filter(p => store.getters['user/hasPermission'](p) === true).length > 0 25 | } else { 26 | // if it doesn't, there is no limit to this path 27 | ok = true 28 | } 29 | if (!ok) { 30 | next({name: 'error_403'}) 31 | } 32 | } 33 | setTitle(to.meta.title) 34 | if (to.name !== LOGIN_PAGE_NAME && !to.query.nonce) { 35 | let nonce = (new Date).getTime() 36 | let query = Object.assign({nonce: nonce}, to.query) 37 | 38 | // See https://github.com/vuejs/vue-router/pull/1906 39 | // 目前在 beforeEach 中无法知晓路由变化是由 push 还是 replace 引发的,这里会 40 | // 导致 replace 失效,如果需要可以暂时在这里 hack 一下,等到 vue-router 问题 41 | // 修复之后,这里可以直接根据调用的是 push 还是 replace 来设置 next 的 replace 42 | // 参数 43 | let replace = false 44 | next({ path: to.path, query: query, replace: replace }) 45 | } else { 46 | next() 47 | } 48 | }) 49 | 50 | router.afterEach(() => { 51 | iView.LoadingBar.finish() 52 | window.scrollTo(0, 0) 53 | }) 54 | 55 | export default router 56 | 57 | -------------------------------------------------------------------------------- /vige-api/vige/api/notifications/tasks.py: -------------------------------------------------------------------------------- 1 | from ...huey_config import huey, app_context, logger 2 | import json 3 | from aliyunsdkcore.request import RpcRequest 4 | from aliyunsdkcore.profile import region_provider 5 | from ..utils import get_acs_client 6 | from ...config import config 7 | 8 | 9 | REGION = "cn-hangzhou" 10 | PRODUCT_NAME = "Dysmsapi" 11 | DOMAIN = "dysmsapi.aliyuncs.com" 12 | 13 | 14 | region_provider.add_endpoint(PRODUCT_NAME, REGION, DOMAIN) 15 | 16 | acs_client = get_acs_client() 17 | 18 | 19 | class AliyunError(Exception): 20 | pass 21 | 22 | 23 | @huey.task(retries=3, retry_delay=20) 24 | @app_context 25 | def send_sms_by_tpl(tpl_name, *mobiles, **tpl_kwargs): 26 | """ 27 | Send sms use aliyun sms sdk 28 | :param tpl_name: aliyun sms template name, like SMS_143660082 29 | :param mobiles: phone numbers, '135xxxxx,136xxxxx', must less than 1000 30 | :param tpl_kwargs: sms template args 31 | :return: 32 | """ 33 | 34 | logger.warning('start to send message to {}'.format(mobiles)) 35 | if not config.get('MESSAGE_SEND_ENABLED'): 36 | logger.info('MESSAGE SEND ENABLED : FALSE') 37 | return 38 | 39 | req = RpcRequest(PRODUCT_NAME, '2017-05-25', 'SendSms') 40 | req.add_query_param('TemplateCode', tpl_name) 41 | req.add_query_param('PhoneNumbers', mobiles) 42 | req.add_query_param('SignName', config.ALY_SMS_SIGN_NAME) 43 | req.add_query_param('TemplateParam', json.dumps(tpl_kwargs)) 44 | 45 | resp = acs_client.do_action_with_exception(req) 46 | resp = json.loads(resp) 47 | if resp.get('Code') != 'OK': 48 | raise AliyunError(resp) 49 | logger.warning(resp) 50 | 51 | 52 | @huey.task(retries=3, retry_delay=20) 53 | @app_context 54 | def send_sms(type_, tpl, *mobiles): 55 | logger.warning('Not implemented') 56 | 57 | -------------------------------------------------------------------------------- /vige-bo/src/view/main/components/side-menu/collapsed-menu.vue: -------------------------------------------------------------------------------- 1 | 12 | 47 | -------------------------------------------------------------------------------- /vige-bo/src/components/Tinymce/dynamicLoadScript.js: -------------------------------------------------------------------------------- 1 | let callbacks = [] 2 | 3 | function loadedTinymce() { 4 | // to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2144 5 | // check is successfully downloaded script 6 | return window.tinymce 7 | } 8 | 9 | const dynamicLoadScript = (src, callback) => { 10 | const existingScript = document.getElementById(src) 11 | const cb = callback || function() {} 12 | 13 | if (!existingScript) { 14 | const script = document.createElement('script') 15 | script.src = src // src url for the third-party library being loaded. 16 | script.id = src 17 | document.body.appendChild(script) 18 | callbacks.push(cb) 19 | const onEnd = 'onload' in script ? stdOnEnd : ieOnEnd 20 | onEnd(script) 21 | } 22 | 23 | if (existingScript && cb) { 24 | if (loadedTinymce()) { 25 | cb(null, existingScript) 26 | } else { 27 | callbacks.push(cb) 28 | } 29 | } 30 | 31 | function stdOnEnd(script) { 32 | script.onload = function() { 33 | // this.onload = null here is necessary 34 | // because even IE9 works not like others 35 | this.onerror = this.onload = null 36 | for (const cb of callbacks) { 37 | cb(null, script) 38 | } 39 | callbacks = null 40 | } 41 | script.onerror = function() { 42 | this.onerror = this.onload = null 43 | // cb(new Error('Failed to load ' + src), script) 44 | } 45 | } 46 | 47 | function ieOnEnd(script) { 48 | script.onreadystatechange = function() { 49 | if (this.readyState !== 'complete' && this.readyState !== 'loaded') return 50 | this.onreadystatechange = null 51 | for (const cb of callbacks) { 52 | cb(null, script) // there is no way to catch loading errors in IE8 53 | } 54 | callbacks = null 55 | } 56 | } 57 | } 58 | 59 | export default dynamicLoadScript 60 | -------------------------------------------------------------------------------- /vige-api/vige/migrations/env.py: -------------------------------------------------------------------------------- 1 | from alembic import context 2 | from sqlalchemy import engine_from_config, pool 3 | from logging.config import fileConfig 4 | import logging 5 | 6 | # 导入你的数据库模块 7 | from vige.db import sm, Base, install 8 | 9 | install() 10 | 11 | # 解析 Alembic 配置文件 12 | config = context.config 13 | 14 | # 配置日志 15 | fileConfig(config.config_file_name) 16 | logger = logging.getLogger('alembic.env') 17 | 18 | # 设置 SQLAlchemy URL 19 | config.set_main_option('sqlalchemy.url', str(sm.engine.url)) 20 | 21 | # 目标元数据 22 | target_metadata = Base.metadata 23 | 24 | 25 | def run_migrations_offline(): 26 | """以离线模式运行迁移。""" 27 | url = config.get_main_option("sqlalchemy.url") 28 | context.configure(url=url, target_metadata=target_metadata, literal_binds=True) 29 | 30 | with context.begin_transaction(): 31 | context.run_migrations() 32 | 33 | 34 | def run_migrations_online(): 35 | """以在线模式运行迁移。""" 36 | # 防止在没有模式更改时生成自动迁移 37 | def process_revision_directives(context, revision, directives): 38 | if getattr(config.cmd_opts, 'autogenerate', False): 39 | script = directives[0] 40 | if script.upgrade_ops.is_empty(): 41 | directives[:] = [] 42 | logger.info('No changes in schema detected.') 43 | 44 | connectable = sm.engine 45 | 46 | with connectable.connect() as connection: 47 | context.configure( 48 | connection=connection, 49 | target_metadata=target_metadata, 50 | process_revision_directives=process_revision_directives 51 | ) 52 | 53 | try: 54 | with context.begin_transaction(): 55 | context.run_migrations() 56 | except Exception as e: 57 | logger.exception('Migrate failed', exc_info=e) 58 | 59 | 60 | if context.is_offline_mode(): 61 | run_migrations_offline() 62 | else: 63 | run_migrations_online() -------------------------------------------------------------------------------- /.github/workflows/backend.yml: -------------------------------------------------------------------------------- 1 | name: Backend CI 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'vige-api/**' 7 | - '.github/workflows/backend.yml' 8 | pull_request: 9 | paths: 10 | - 'vige-api/**' 11 | 12 | jobs: 13 | test: 14 | runs-on: ubuntu-latest 15 | services: 16 | postgres: 17 | image: postgres:14 18 | env: 19 | POSTGRES_DB: vige 20 | POSTGRES_USER: postgres 21 | POSTGRES_PASSWORD: postgres 22 | ports: ['5432:5432'] 23 | options: >- 24 | --health-cmd "pg_isready -U postgres" 25 | --health-interval 10s 26 | --health-timeout 5s 27 | --health-retries 5 28 | redis: 29 | image: redis:6 30 | ports: ['6379:6379'] 31 | 32 | steps: 33 | - uses: actions/checkout@v4 34 | 35 | - name: Set up Python 36 | uses: actions/setup-python@v5 37 | with: 38 | python-version: '3.10' 39 | 40 | - name: Install pipenv 41 | run: pip install pipenv==2023.11.14 42 | 43 | - name: Install backend deps 44 | working-directory: vige-api 45 | run: | 46 | pipenv sync --dev 47 | 48 | - name: Create env file 49 | working-directory: vige-api/vige 50 | run: | 51 | cat > local_config.env << 'EOF' 52 | ENV=ci 53 | DEBUG=true 54 | SECRET_KEY=test 55 | SQLALCHEMY_DATABASE_URI=postgresql://postgres:postgres@localhost:5432/vige 56 | REDIS_HOST=localhost 57 | REDIS_PORT=6379 58 | AUTHJWT_SECRET_KEY=testjwt 59 | EXTERNAL_URL=http://localhost:8000 60 | EOF 61 | 62 | - name: Smoke import (temporary until tests are stabilized) 63 | working-directory: vige-api 64 | env: 65 | PYTHONPATH: . 66 | run: | 67 | pipenv run python -c "import vige.app; print('backend import ok')" 68 | 69 | -------------------------------------------------------------------------------- /vige-bo/src/components/markdown/markdown.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 63 | 64 | 77 | -------------------------------------------------------------------------------- /vige-web/src/views/main/components/header-bar/header-bar.vue: -------------------------------------------------------------------------------- 1 | 16 | 46 | 47 | -------------------------------------------------------------------------------- /vige-bo/src/components/info-card/infor-card.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 55 | 56 | 98 | -------------------------------------------------------------------------------- /vige-api/vige/api/wechat/tasks.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from huey import crontab 4 | 5 | from ...huey_config import huey, app_context 6 | from ...app_factory import get_redis_client 7 | from .wechat import wechat, ACCESS_TOKEN_KEY, JSAPI_TICKET_KEY 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | @huey.periodic_task(crontab(minute='*/5')) 13 | @huey.lock_task('wechat-token-check-lock') 14 | @app_context 15 | def check_wx_tokens(): 16 | """ 17 | check access token and js api ticket every 5 minute, if the expires_in 18 | is less then 6 minutes, refresh it 19 | """ 20 | redis_client = get_redis_client() 21 | access_token_ttl = redis_client.ttl(ACCESS_TOKEN_KEY) 22 | if not access_token_ttl or access_token_ttl < 360: 23 | refresh_wx_token() 24 | jsapi_ticket_ttl = redis_client.ttl(JSAPI_TICKET_KEY) 25 | if not jsapi_ticket_ttl or jsapi_ticket_ttl < 360: 26 | refresh_jsapi_ticket() 27 | 28 | 29 | @huey.task(retries=5, retry_delay=5) 30 | @huey.lock_task('wechat-token-lock') 31 | @app_context 32 | def refresh_wx_token(): 33 | app_id = wechat.app_id 34 | app_secret = wechat.app_secret 35 | resp = wechat.get('/cgi-bin/token', { 36 | 'grant_type': 'client_credential', 37 | 'appid': app_id, 38 | 'secret': app_secret 39 | }) 40 | token = resp.get('access_token') 41 | expires_in = resp.get('expires_in') 42 | if token: 43 | redis_client = get_redis_client() 44 | redis_client.set(ACCESS_TOKEN_KEY, token, ex=expires_in) 45 | 46 | 47 | @huey.task(retries=5, retry_delay=5) 48 | @huey.lock_task('wechat-jspai-ticket') 49 | @app_context 50 | def refresh_jsapi_ticket(): 51 | token = wechat.access_token 52 | resp = wechat.get('/cgi-bin/ticket/getticket', { 53 | 'access_token': token, 54 | 'type': 'jsapi' 55 | }) 56 | ticket = resp.get('ticket') 57 | expires_in = resp.get('expires_in') 58 | logger.warning(f'{ticket}, {expires_in}') 59 | if ticket: 60 | redis_client = get_redis_client() 61 | redis_client.set(JSAPI_TICKET_KEY, ticket, ex=expires_in) 62 | -------------------------------------------------------------------------------- /vige-web/src/views/main/components/user/user.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 85 | -------------------------------------------------------------------------------- /vige-api/vige/log.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import logging 3 | from logging.handlers import SMTPHandler 4 | 5 | 6 | class Formatter(logging.Formatter): 7 | 8 | def formatException(self, ei): 9 | s = super().formatException(ei) 10 | tb = ei[-1] 11 | if tb: 12 | while tb.tb_next: 13 | tb = tb.tb_next 14 | local_vars = tb.tb_frame.f_locals 15 | s = '{}\nLocal variables:\n{}'.format( 16 | s, '\n'.join(f' {k}: {v}' for k, v in local_vars.items()) 17 | ) 18 | return s 19 | 20 | 21 | def configure_logging(config): 22 | if config.DEBUG: 23 | logging.basicConfig(level=logging.DEBUG) 24 | else: 25 | # root logger: INFO to stdout; ERROR to mail 26 | root_logger = logging.getLogger() 27 | root_logger.setLevel(logging.INFO) 28 | # log to stdout 29 | stream_handler = logging.StreamHandler() 30 | stream_handler.setFormatter(Formatter( 31 | '%(name)s %(levelname)s in %(pathname)s:%(lineno)s: %(message)s' 32 | )) 33 | stream_handler.setLevel(logging.INFO) 34 | root_logger.addHandler(stream_handler) 35 | # mail 36 | if config.LOGGING_MAIL_SERVER: 37 | if 'gunicorn' in sys.argv[0]: 38 | proc = 'api' 39 | elif 'huey' in sys.argv[0]: 40 | proc = 'worker' 41 | elif 'run_sio' in sys.argv[0]: 42 | proc = 'socketio' 43 | else: 44 | proc = 'other' 45 | mail_handler = SMTPHandler( 46 | mailhost=config.LOGGING_MAIL_SERVER, 47 | fromaddr=config.LOGGING_MAIL_FROM, 48 | toaddrs=config.LOGGING_MAIL_TO_LIST, 49 | subject=f'[{config.ENV.upper()}][{proc}]vige Error', 50 | ) 51 | mail_handler.setLevel(logging.ERROR) 52 | mail_handler.setFormatter(Formatter( 53 | '%(asctime)s [%(name)s]%(levelname)s in %(pathname)s:%(lineno)s %(message)s' 54 | # noqa 55 | )) 56 | root_logger.addHandler(mail_handler) 57 | -------------------------------------------------------------------------------- /vige-api/vige/api/bo_user/forms.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from pydantic import BaseModel, field_validator, ValidationError 3 | from ..forms import BaseFilterForm 4 | 5 | 6 | class LoginForm(BaseModel): 7 | username: str 8 | password: str 9 | 10 | 11 | class TwoFAForm(BaseModel): 12 | code: str 13 | 14 | 15 | class RoleListFilterForm(BaseFilterForm): 16 | keyword: Optional[str] = None 17 | 18 | 19 | class RoleForm(BaseModel): 20 | id: Optional[int] = None 21 | name: str 22 | description: Optional[str] = None 23 | permissions: list 24 | 25 | 26 | class UpdatePasswordForm(BaseModel): 27 | password: str 28 | new_password: str 29 | password_verify: str 30 | 31 | @field_validator('password_verify') 32 | def password_verify(cls, v, values, **kwargs): 33 | if 'new_password' in values and v != values['new_password']: 34 | raise ValueError('两次输入密码必须一致') 35 | return v 36 | 37 | 38 | class UserListFilterForm(BaseFilterForm): 39 | keyword: Optional[str] = None 40 | role_id: Optional[int] = None 41 | active: Optional[int] = None 42 | bound_wechat: Optional[int] = None 43 | 44 | 45 | class UserForm(BaseModel): 46 | id: Optional[int] = None 47 | nickname: Optional[str] = None 48 | username: str 49 | password: str 50 | confirm_password: str 51 | mobile: str 52 | role_id: int 53 | active: Optional[bool] = None 54 | 55 | @field_validator('password') 56 | def validate_password(cls, v, values): 57 | confirm_password = values.get('confirm_password') 58 | if confirm_password and v != confirm_password: 59 | raise ValueError('两次输入密码必须一致') 60 | if not values.get('id') and not v: 61 | raise ValueError('密码为必填项') 62 | 63 | @field_validator('confirm_password') 64 | def validate_confirm_password(cls, v, values): 65 | if not values.get('id') and not v: 66 | raise ValueError('确认密码为必填项') 67 | 68 | 69 | def validate_user_form(data): 70 | try: 71 | form = UserForm(**data) 72 | return dict(success=True, data=form.dict()) 73 | except ValidationError as e: 74 | return dict(success=False, message='验证失败', errors=e.errors()) 75 | -------------------------------------------------------------------------------- /vige-bo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iview-admin", 3 | "version": "2.0.0", 4 | "author": "Lison", 5 | "private": false, 6 | "scripts": { 7 | "dev": "vue-cli-service serve --open", 8 | "build": "vue-cli-service build", 9 | "lint": "vue-cli-service lint", 10 | "test:unit": "vue-cli-service test:unit", 11 | "test:e2e": "vue-cli-service test:e2e" 12 | }, 13 | "dependencies": { 14 | "@websanova/vue-auth": "^2.21.14-beta", 15 | "@xkeshi/vue-qrcode": "^1.0.0", 16 | "axios": "^0.18.0", 17 | "babel-preset-es2015": "^6.24.1", 18 | "clipboard": "^2.0.0", 19 | "codemirror": "^5.38.0", 20 | "countup": "^1.8.2", 21 | "cropperjs": "^1.2.2", 22 | "echarts": "^4.0.4", 23 | "eslint-plugin-html": "^4.0.5", 24 | "file-saver": "^1.3.8", 25 | "html2canvas": "^1.0.0-alpha.12", 26 | "iview": "^3.0.0", 27 | "compressorjs": "^1.0.6", 28 | "iview-area": "^1.5.17", 29 | "js-cookie": "^2.2.0", 30 | "moment": "^2.22.2", 31 | "simplemde": "^1.11.2", 32 | "sortablejs": "^1.7.0", 33 | "vue": "^2.5.10", 34 | "vue-axios": "^2.1.3", 35 | "vue-i18n": "^7.8.0", 36 | "vue-router": "^3.0.1", 37 | "vuex": "^3.0.1", 38 | "wangeditor": "^3.1.1", 39 | "xlsx": "^0.13.3" 40 | }, 41 | "devDependencies": { 42 | "@vue/cli-plugin-babel": "^3.0.0-beta.10", 43 | "@vue/cli-plugin-eslint": "^3.0.0-beta.10", 44 | "@vue/cli-plugin-unit-mocha": "^3.0.0-beta.10", 45 | "@vue/cli-service": "^3.0.0-beta.10", 46 | "@vue/eslint-config-standard": "^3.0.0-beta.10", 47 | "@vue/test-utils": "^1.0.0-beta.10", 48 | "chai": "^4.1.2", 49 | "eslint-plugin-cypress": "^2.0.1", 50 | "less": "^2.7.3", 51 | "less-loader": "^4.0.5", 52 | "lint-staged": "^6.0.0", 53 | "mockjs": "^1.0.1-beta3", 54 | "vue-template-compiler": "^2.5.13" 55 | }, 56 | "browserslist": [ 57 | "> 1%", 58 | "last 2 versions", 59 | "not ie <= 8" 60 | ], 61 | "gitHooks": { 62 | "pre-commit": "lint-staged" 63 | }, 64 | "lint-staged": { 65 | "*.js": [ 66 | "vue-cli-service lint", 67 | "git add" 68 | ], 69 | "*.vue": [ 70 | "vue-cli-service lint", 71 | "git add" 72 | ] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /vige-wechat/src/views/bind-bo-user.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 87 | -------------------------------------------------------------------------------- /vige-api/vige/test_utils.py: -------------------------------------------------------------------------------- 1 | import random 2 | import httpx 3 | from fastapi.testclient import TestClient 4 | import factory 5 | from .db import sm 6 | from fastapi import FastAPI 7 | 8 | app = FastAPI() 9 | 10 | class APIClient: 11 | def __init__(self, app: FastAPI): 12 | self.client = TestClient(app) 13 | self.cookie = None 14 | self.csrf_cookie = None 15 | 16 | def open(self, method, url, *args, **kwargs): 17 | headers = kwargs.pop('headers', {}) 18 | if 'json' in kwargs: # API call 19 | assert 'data' not in kwargs, 'cannot set data and json at the same time' 20 | kwargs['data'] = kwargs.pop('json') 21 | headers['content-type'] = 'application/json' 22 | if self.cookie: 23 | if 'Cookie' not in headers: 24 | headers['Cookie'] = self.cookie 25 | if self.csrf_cookie: 26 | headers['X-CSRF-TOKEN'] = self.csrf_cookie 27 | 28 | response = self.client.request(method, url, headers=headers, *args, **kwargs) 29 | return response 30 | 31 | def logout(self): 32 | self.cookie = None 33 | self.csrf_cookie = None 34 | 35 | 36 | class BoClient(APIClient): 37 | def login(self, username, password): 38 | resp = self.post('/v1/admin/login', json={ 39 | 'username': username, 40 | 'password': password, 41 | }) 42 | assert resp.status_code == 200, resp.json 43 | cookies = resp.headers.getlist('Set-Cookie') 44 | for cookie in cookies: 45 | if cookie.startswith('vige_auth_cookie='): 46 | self.cookie = cookie 47 | elif cookie.startswith('vige_auth_csrf_cookie='): 48 | self.csrf_cookie = cookie.replace( 49 | 'vige_auth_csrf_cookie=', '').split(';')[0] 50 | 51 | 52 | 53 | class BaseFactory(factory.alchemy.SQLAlchemyModelFactory): 54 | class Meta: 55 | abstract = True 56 | sqlalchemy_session = sm.get_db() 57 | 58 | 59 | class Sequence(factory.Sequence): 60 | def __init__(self, namespace='', randomize=True): 61 | if randomize: 62 | prefix = '{}-{:05d}'.format(namespace, random.randint(0, 10000)) 63 | else: 64 | prefix = namespace 65 | super().__init__(lambda n: f'{prefix}-{n}') -------------------------------------------------------------------------------- /vige-web/src/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import {get} from 'lodash' 4 | import routers from '@/router/routers' 5 | import {getBreadCrumbList, getMenuByRouter, getHomeRoute} from '@/libs/util' 6 | import {api} from './libs/api' 7 | 8 | Vue.use(Vuex) 9 | 10 | export default new Vuex.Store({ 11 | state: { 12 | breadCrumbList: [], 13 | homeRoute: getHomeRoute(routers), 14 | loading: false, 15 | fixtures: {}, 16 | user: {} 17 | }, 18 | mutations: { 19 | setBreadCrumb (state, routeMetched) { 20 | state.breadCrumbList = getBreadCrumbList(routeMetched, state.homeRoute) 21 | }, 22 | 23 | setLoading (state, value) { 24 | state.loading = value 25 | }, 26 | 27 | setUser (state, user) { 28 | if (!user) { 29 | user = {} 30 | } 31 | state.user = Object.assign({}, user) 32 | }, 33 | 34 | setFixtures (state, fixtures) { 35 | state.fixtures = fixtures 36 | } 37 | }, 38 | 39 | getters: { 40 | user: state => state.user, 41 | fixtures: state => state.fixtures, 42 | authorized: state => !!state.user.id, 43 | }, 44 | 45 | actions: { 46 | getFixtures ({ commit }) { 47 | return api.get('/admin/fixtures').then(resp => { 48 | commit('setFixtures', resp.fixtures) 49 | }) 50 | }, 51 | 52 | authSuccess ({ commit }, { $auth }) { 53 | commit('setUser', $auth.user()) 54 | }, 55 | 56 | login ({ commit, dispatch }, { vue, loginData }) { 57 | // let redirect = vue.$auth.redirect() 58 | return vue.$auth.login({ 59 | data: loginData, 60 | rememberMe: true, 61 | redirect: null, 62 | fetchUser: false, // we'll manually fetch if this is not 2FA login 63 | success (res) { 64 | vue.$auth.fetch({ 65 | success: async () => { 66 | await dispatch('refreshUser', vue) 67 | // await dispatch('getConfigs', vue) 68 | vue.$router.replace({ name: 'home' }) 69 | } 70 | }) 71 | } 72 | }) 73 | }, 74 | 75 | 76 | logout ({ commit }, { vue, makeRequest = true }) { 77 | commit('setUser') 78 | vue.$auth.logout({ 79 | makeRequest: makeRequest, 80 | redirect: { name: 'login' } 81 | }) 82 | }, 83 | 84 | async refreshUser ({ commit }) { 85 | let resp = await api.get('/web/users/me') 86 | commit('setUser', resp.user) 87 | } 88 | } 89 | }) 90 | -------------------------------------------------------------------------------- /vige-bo/src/libs/api.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import iView from 'iview' 3 | import { get } from 'lodash' 4 | import router from '../router' 5 | import store from '../store' 6 | import { getCSRFToken } from './util' 7 | 8 | export const api = axios.create({ 9 | baseURL: '/v1', 10 | timeout: 30000, 11 | }) 12 | 13 | api.interceptors.request.use(config => { 14 | config.headers.source = 'bo' 15 | // 为变更类请求自动附加 CSRF 头 16 | const method = (config.method || 'get').toLowerCase() 17 | if (method === 'post' || method === 'put' || method === 'patch' || method === 'delete') { 18 | const csrf = getCSRFToken() 19 | if (csrf) config.headers['X-CSRF-TOKEN'] = csrf 20 | } 21 | config.params = Object.assign(config.params || {}, {nonce: Date.now()}) 22 | if (!config.hideProgress) { 23 | iView.LoadingBar.start() 24 | } 25 | return config 26 | }, (error) => { 27 | iView.LoadingBar.error() 28 | return Promise.reject(error) 29 | }) 30 | 31 | api.interceptors.response.use(response => { 32 | if (!response.config.hideProgress) { 33 | iView.LoadingBar.finish() 34 | } 35 | return response.data 36 | }, (error) => { 37 | iView.LoadingBar.error() 38 | // 兼容多种后端错误结构:{message}, {error: {message}}, {detail} 39 | let errMsg = get(error, 'response.data.message') || 40 | get(error, 'response.data.error.message') || 41 | get(error, 'response.data.detail') || 42 | '请求失败' 43 | iView.Message.error({ 44 | content: errMsg, 45 | duration: 2.5, 46 | }) 47 | 48 | let status = get(error, 'response.status') 49 | if (status === 401) { 50 | store.dispatch('user/logout', {vue: router.app, makeRequest: true}) 51 | } 52 | 53 | // normally if a user don't have permission to a page, he won't even be able 54 | // to see the page, as a result he won't hit any API which he has no access to 55 | // 56 | // if a user paste the url of a forbidden page when the vue app is already 57 | // fully loaded, vue knows he has no permission so redirects him to 403 page 58 | // directly 59 | // 60 | // in case a user paste the url of a forbidden page in a **new tab** 61 | // and vue-auth hasn't got back user permission info yet, 62 | // he can actually go to the page and fire some API calls 63 | // The calls would return 403s and we redirect him to 403.vue 64 | if (status === 403 && (!router.currentRoute || router.currentRoute.name !== 'error_403')) { 65 | router.push({name: 'error_403'}) 66 | // TODO: refresh user 67 | } 68 | return Promise.reject(error) 69 | }) 70 | -------------------------------------------------------------------------------- /vige-web/src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 45 | 46 | 54 | 55 | 118 | -------------------------------------------------------------------------------- /vige-api/vige/api/settings/base.py: -------------------------------------------------------------------------------- 1 | from fastapi import Depends, Request, Response 2 | from sqlalchemy.orm import Session 3 | from ...db import sm 4 | from .fields import BaseField 5 | from .models import Settings as Model 6 | 7 | 8 | class _None: 9 | pass 10 | 11 | 12 | class SettingsBase: 13 | def __init__(self): 14 | self._cache = {} 15 | self._empty = True 16 | 17 | def get(self, item, hook=True, load_all=True): 18 | field = super().__getattribute__(item) 19 | if isinstance(field, BaseField): 20 | key = item 21 | if load_all and self._empty: 22 | self._empty = False 23 | with sm.transaction_scope() as db: 24 | q = db.query(Model) 25 | for model in q: 26 | self._cache[model.key] = model.value 27 | rv = self._cache.get(key, _None) 28 | if rv is _None: 29 | model = None 30 | with sm.transaction_scope() as db: 31 | if self._empty: 32 | model = db.query(Model).filter(Model.key == key).one_or_none() 33 | if model is None: 34 | rv = field.default 35 | else: 36 | self._cache[key] = model.value 37 | rv = field.deserialize(model.value) 38 | else: 39 | rv = field.deserialize(rv) 40 | if hook: 41 | rv = getattr(self, 'get_' + item, 42 | lambda x: field.after_get(item, key, x))(rv) 43 | return rv 44 | return field 45 | 46 | def __getattribute__(self, item): 47 | get = super().__getattribute__('get') 48 | return get if item == 'get' else get(item) 49 | 50 | def __setattr__(self, item, value): 51 | field = getattr(self.__class__, item, None) 52 | if isinstance(field, BaseField): 53 | hook = getattr(self, 'set_' + item, None) 54 | key = item 55 | if hook is not None: 56 | value = hook(value) 57 | data = field.serialize(value) 58 | with sm.transaction_scope() as sa: 59 | model = sa.query(Model).filter(Model.key == key).with_for_update().one_or_none() 60 | if model: 61 | model.value = data 62 | else: 63 | Model.create(sa, key=key, value=data) 64 | self._cache[key] = data 65 | else: 66 | self.__dict__[item] = value 67 | 68 | -------------------------------------------------------------------------------- /vige-wechat/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 43 | 44 | 45 | 61 | --------------------------------------------------------------------------------