├── __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 |
2 |
3 |
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 |
2 |
3 |
4 |
5 |
20 |
--------------------------------------------------------------------------------
/vige-bo/src/view/error-page/500.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
2 |
3 |
4 |
5 |
20 |
--------------------------------------------------------------------------------
/vige-bo/src/view/error-page/403.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
2 |
3 |
首页
4 | 显示工作台
5 |
6 |
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 |
2 |
5 |
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 |
2 |
3 |
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 |
2 |
3 |
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 |
2 |
3 |
4 |
![404]()
5 |
6 |
{{ code }}
7 | {{ desc }}
8 |
9 |
10 |
11 |
12 |
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 |
2 |
3 |
4 |
25 |
28 |
--------------------------------------------------------------------------------
/vige-web/src/views/main/components/header-bar/sider-trigger/sider-trigger.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
14 |
15 |
16 | {{ content }}
17 |
20 |
21 |
22 |
23 |
34 |
--------------------------------------------------------------------------------
/vige-bo/src/components/tables/poptip-btn.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
39 |
40 |
--------------------------------------------------------------------------------
/vige-bo/src/view/single-page/home/home.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ title }}
3 |
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 |
2 |
7 |
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 |
2 |
3 |
4 |
5 |
6 |
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 |
2 |
3 |
微信工作台
4 |
显示首页
5 |
![]()
6 |
7 |
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 |
2 |
3 |
4 |
5 | 载入中...
6 |
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 |
2 |
7 |
8 |
9 |
40 |
41 |
44 |
--------------------------------------------------------------------------------
/vige-bo/src/view/main/components/header-bar/header-bar.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
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 |
2 |
3 |
4 |
5 |
39 |
40 |
43 |
--------------------------------------------------------------------------------
/vige-web/src/components/common-icon/common-icon.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
39 |
40 |
43 |
--------------------------------------------------------------------------------
/vige-bo/src/components/tables/confirm-button.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
10 |
13 |
14 |
15 |
16 |
47 |
--------------------------------------------------------------------------------
/vige-bo/src/view/login/login.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
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 |
2 |
5 |
6 |
![]()
7 |
8 |
9 |
10 |
11 |
31 |
32 |
--------------------------------------------------------------------------------
/vige-bo/src/components/charts/bar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
2 |
13 |
14 |
15 |
52 |
--------------------------------------------------------------------------------
/vige-web/src/views/main/components/language/language.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
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 |
2 |
3 |
4 |
5 |
6 | {{ showTitle(item) }}
7 |
8 |
9 |
10 |
11 |
47 |
--------------------------------------------------------------------------------
/vige-bo/src/view/main/components/side-menu/side-menu-item.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ showTitle(parentItem) }}
6 |
7 |
8 |
9 |
10 | {{ showTitle(item.children[0]) }}
11 |
12 |
13 |
14 | {{ showTitle(item) }}
15 |
16 |
17 |
18 |
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 |
2 |
3 |
4 |
5 | 首页
6 |
7 |
8 |
9 | {{ showTitle(item) }}
10 |
11 |
12 |
13 |
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 |
2 |
3 |
4 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
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 |
2 |
3 |
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 |
12 |
20 |
22 |
23 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
63 |
64 |
77 |
--------------------------------------------------------------------------------
/vige-web/src/views/main/components/header-bar/header-bar.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
46 |
47 |
--------------------------------------------------------------------------------
/vige-bo/src/components/info-card/infor-card.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
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 |
2 |
16 |
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 |
2 |
3 |
4 |
5 | 载入中...
6 |
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 |
2 |
3 |
{{ msg }}
4 |
5 | For guide and recipes on how to configure / customize this project,
6 | check out the
7 | vue-cli documentation.
8 |
9 |
Installed CLI Plugins
10 |
16 |
Essential Links
17 |
24 |
Ecosystem
25 |
32 |
33 |
34 |
35 |
43 |
44 |
45 |
61 |
--------------------------------------------------------------------------------