├── .browserslistrc ├── README.md ├── .env.development ├── dist.zip ├── .gitattributes ├── jest.config.js ├── .postcssrc.js ├── src ├── views │ ├── system │ │ ├── login │ │ │ ├── index.js │ │ │ └── image │ │ │ │ ├── logo@2x.png │ │ │ │ └── login-code.png │ │ ├── function │ │ │ ├── refresh │ │ │ │ └── index.js │ │ │ └── redirect │ │ │ │ └── index.js │ │ ├── error │ │ │ └── 404 │ │ │ │ └── index.vue │ │ ├── index │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ ├── permission.vue │ │ │ │ └── editor.vue │ │ └── log │ │ │ └── index.vue │ └── management │ │ ├── user │ │ └── style.scss │ │ ├── role │ │ ├── form2.js │ │ └── form.js │ │ ├── post │ │ └── form.js │ │ ├── config │ │ └── form.js │ │ ├── dict │ │ └── form.js │ │ └── dept │ │ └── form.js ├── assets │ ├── style │ │ ├── theme │ │ │ ├── d2 │ │ │ │ ├── index.scss │ │ │ │ └── setting.scss │ │ │ ├── line │ │ │ │ ├── index.scss │ │ │ │ └── setting.scss │ │ │ ├── star │ │ │ │ ├── index.scss │ │ │ │ └── setting.scss │ │ │ ├── tomorrow-night-blue │ │ │ │ ├── index.scss │ │ │ │ └── setting.scss │ │ │ ├── violet │ │ │ │ ├── index.scss │ │ │ │ └── setting.scss │ │ │ └── register.scss │ │ ├── fixed │ │ │ ├── n-progress.scss │ │ │ └── base.scss │ │ ├── unit │ │ │ └── color.scss │ │ ├── animate │ │ │ └── vue-transition.scss │ │ ├── public.scss │ │ └── public-class.scss │ └── svg-icons │ │ ├── index.js │ │ └── icons │ │ └── d2-admin.svg ├── layout │ └── header-aside │ │ ├── index.js │ │ ├── components │ │ ├── header-base-url │ │ │ └── index.vue │ │ ├── header-search │ │ │ └── index.vue │ │ ├── mixin │ │ │ └── menu.js │ │ ├── header-fullscreen │ │ │ └── index.vue │ │ ├── header-user │ │ │ └── index.vue │ │ ├── header-locales │ │ │ └── index.vue │ │ ├── header-theme │ │ │ ├── index.vue │ │ │ └── components │ │ │ │ └── d2-theme-list │ │ │ │ └── index.vue │ │ ├── utils │ │ │ └── menu.js │ │ ├── header-log │ │ │ └── index.vue │ │ ├── contextmenu │ │ │ ├── components │ │ │ │ └── contentmenuList │ │ │ │ │ └── index.vue │ │ │ └── index.vue │ │ ├── menu-side │ │ │ └── index.js │ │ └── panel-search │ │ │ └── components │ │ │ └── panel-search-item │ │ │ └── index.vue │ │ └── mixins │ │ └── search.js ├── utils │ ├── modules │ │ ├── import-development.js │ │ ├── import-production.js │ │ ├── time.js │ │ ├── fn.js │ │ ├── cookies.js │ │ ├── log.js │ │ └── db.js │ ├── snippets │ │ ├── snippets.json │ │ ├── fn-snippets.json │ │ ├── dayjs-snippets.json │ │ ├── cookies-snippets.json │ │ ├── db-snippets.json │ │ └── log-snippets.json │ └── index.js ├── plugin │ ├── api │ │ └── index.js │ ├── env │ │ └── index.js │ ├── lodash │ │ └── index.js │ ├── open │ │ └── index.js │ ├── permission │ │ └── index.js │ ├── log │ │ └── index.js │ ├── error │ │ └── index.js │ └── d2admin │ │ └── index.js ├── filters │ ├── modules │ │ └── time.js │ ├── index.js │ └── snippets │ │ └── time-snippets.js ├── components │ ├── d2-fork-me-on-github │ │ ├── image │ │ │ └── darkblue@2x.png │ │ ├── snippets.json │ │ └── component.vue │ ├── d2-icon │ │ ├── font-awesome-4.7.0 │ │ │ └── fonts │ │ │ │ ├── FontAwesome.otf │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ └── fontawesome-webfont.woff2 │ │ ├── snippets.json │ │ └── component.vue │ ├── d2-vnode │ │ ├── component.js │ │ └── snippets.json │ ├── d2-bar-space │ │ ├── snippets.json │ │ └── component.js │ ├── d2-time-relative │ │ ├── snippets.json │ │ └── component.vue │ ├── d2-bar-cell │ │ ├── snippets.json │ │ └── component.js │ ├── d2-export-table │ │ ├── snippets.json │ │ └── component.vue │ ├── d2-icon-svg │ │ ├── snippets.json │ │ └── component.vue │ ├── d2-bar │ │ ├── component.js │ │ └── snippets.json │ ├── d2-tencent-captcha │ │ ├── snippets.json │ │ └── component.vue │ ├── d2-api-base-url-controller │ │ └── snippets.json │ ├── d2-dict │ │ └── component.js │ ├── d2-dict-select │ │ └── component.js │ ├── index.js │ ├── d2-container │ │ └── components │ │ │ ├── d2-container-ghost-bs.vue │ │ │ ├── d2-container-full-bs.vue │ │ │ ├── d2-container-ghost.vue │ │ │ ├── d2-container-card-bs.vue │ │ │ ├── d2-container-full.vue │ │ │ ├── d2-container-card.vue │ │ │ ├── mixins │ │ │ ├── bs.js │ │ │ └── normal.js │ │ │ └── d2-source.vue │ ├── d2-pagination │ │ ├── snippets.json │ │ └── component.vue │ ├── d2-dict-radio │ │ └── component.js │ ├── d2-table │ │ ├── snippets.json │ │ └── component.js │ ├── d2-scrollbar │ │ └── component.vue │ ├── d2-card │ │ └── component.vue │ ├── d2-drawer-container │ │ └── component.vue │ ├── d2-throttle │ │ └── component.js │ ├── d2-table-columns-fixed-controller │ │ └── component.vue │ ├── d2-table-actions │ │ └── component.js │ ├── d2-debounce │ │ └── component.js │ ├── d2-button │ │ └── component.js │ ├── d2-search-panel │ │ └── component.vue │ └── d2-avatar-uploader │ │ └── component.vue ├── mixins │ ├── el-field-change.js │ ├── crud-permission.js │ ├── crud-form-helper.js │ ├── component-vmodel.js │ ├── component-multiple.js │ ├── crud-form-dialog.js │ ├── crud-status.js │ ├── crud-dict.js │ └── crud-pagination.js ├── locales │ ├── zh-chs.json │ ├── zh-cht.json │ ├── ja.json │ ├── en.json │ └── mixin.js ├── store │ ├── modules │ │ └── d2admin │ │ │ ├── index.js │ │ │ ├── modules │ │ │ ├── ua.js │ │ │ ├── releases.js │ │ │ ├── gray.js │ │ │ ├── sys.js │ │ │ ├── fullscreen.js │ │ │ ├── transition.js │ │ │ ├── search.js │ │ │ ├── size.js │ │ │ ├── log.js │ │ │ ├── theme.js │ │ │ ├── dict.js │ │ │ └── menu.js │ │ │ └── snippets │ │ │ └── snippets.js │ ├── index.js │ └── snippets │ │ └── snippets.js ├── api │ ├── modules │ │ ├── captcha.js │ │ ├── upload.js │ │ ├── dict.js │ │ ├── post.js │ │ ├── role.js │ │ ├── dept.js │ │ ├── dict-data.js │ │ ├── config.js │ │ ├── menu.js │ │ └── user.js │ ├── index.js │ └── service.js ├── snippets │ └── log-snippets.json ├── App.vue ├── env.js ├── permission.js ├── main.js ├── i18n.js └── setting.js ├── public ├── icon.ico ├── image │ ├── theme │ │ ├── line │ │ │ ├── bg.jpg │ │ │ ├── logo │ │ │ │ ├── all.png │ │ │ │ └── icon-only.png │ │ │ └── preview@2x.png │ │ ├── star │ │ │ ├── bg.jpg │ │ │ ├── logo │ │ │ │ ├── all.png │ │ │ │ └── icon-only.png │ │ │ └── preview@2x.png │ │ ├── d2 │ │ │ ├── logo │ │ │ │ ├── all.png │ │ │ │ └── icon-only.png │ │ │ └── preview@2x.png │ │ ├── violet │ │ │ ├── logo │ │ │ │ ├── all.png │ │ │ │ └── icon-only.png │ │ │ └── preview@2x.png │ │ └── tomorrow-night-blue │ │ │ ├── logo │ │ │ ├── all.png │ │ │ └── icon-only.png │ │ │ └── preview@2x.png │ └── loading │ │ └── loading-spin.svg └── index.html ├── tests └── unit │ └── .eslintrc.js ├── babel.config.js ├── .eslintignore ├── .env.netlify ├── .env.github ├── .env.preview ├── jsconfig.json ├── .gitignore ├── .eslintrc.js ├── LICENSE ├── tools └── dir-exists.js ├── .env ├── package.json └── dependencies-cdn.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # d2admin-permission 2 | 与flask-permission配套的前端 3 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # 开发环境 2 | 3 | # 页面 title 前缀 4 | VUE_APP_TITLE=D2Admin Dev 5 | -------------------------------------------------------------------------------- /dist.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/dist.zip -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@vue/cli-plugin-unit-jest' 3 | } 4 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/views/system/login/index.js: -------------------------------------------------------------------------------- 1 | import page from './page' 2 | 3 | export default page 4 | -------------------------------------------------------------------------------- /public/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/public/icon.ico -------------------------------------------------------------------------------- /src/assets/style/theme/d2/index.scss: -------------------------------------------------------------------------------- 1 | @import './setting.scss'; 2 | @import '../theme.scss'; 3 | -------------------------------------------------------------------------------- /src/assets/style/theme/line/index.scss: -------------------------------------------------------------------------------- 1 | @import './setting.scss'; 2 | @import '../theme.scss'; 3 | -------------------------------------------------------------------------------- /src/assets/style/theme/star/index.scss: -------------------------------------------------------------------------------- 1 | @import './setting.scss'; 2 | @import '../theme.scss'; 3 | -------------------------------------------------------------------------------- /src/layout/header-aside/index.js: -------------------------------------------------------------------------------- 1 | import layout from './layout' 2 | 3 | export default layout 4 | -------------------------------------------------------------------------------- /src/utils/modules/import-development.js: -------------------------------------------------------------------------------- 1 | module.exports = file => require('@/views/' + file).default 2 | -------------------------------------------------------------------------------- /src/utils/modules/import-production.js: -------------------------------------------------------------------------------- 1 | module.exports = file => () => import('@/views/' + file) 2 | -------------------------------------------------------------------------------- /src/assets/style/theme/tomorrow-night-blue/index.scss: -------------------------------------------------------------------------------- 1 | @import './setting.scss'; 2 | @import '../theme.scss'; 3 | -------------------------------------------------------------------------------- /public/image/theme/line/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/public/image/theme/line/bg.jpg -------------------------------------------------------------------------------- /public/image/theme/star/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/public/image/theme/star/bg.jpg -------------------------------------------------------------------------------- /public/image/theme/d2/logo/all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/public/image/theme/d2/logo/all.png -------------------------------------------------------------------------------- /public/image/theme/d2/preview@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/public/image/theme/d2/preview@2x.png -------------------------------------------------------------------------------- /public/image/theme/line/logo/all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/public/image/theme/line/logo/all.png -------------------------------------------------------------------------------- /public/image/theme/star/logo/all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/public/image/theme/star/logo/all.png -------------------------------------------------------------------------------- /public/image/theme/line/preview@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/public/image/theme/line/preview@2x.png -------------------------------------------------------------------------------- /public/image/theme/star/preview@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/public/image/theme/star/preview@2x.png -------------------------------------------------------------------------------- /public/image/theme/violet/logo/all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/public/image/theme/violet/logo/all.png -------------------------------------------------------------------------------- /public/image/theme/d2/logo/icon-only.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/public/image/theme/d2/logo/icon-only.png -------------------------------------------------------------------------------- /public/image/theme/line/logo/icon-only.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/public/image/theme/line/logo/icon-only.png -------------------------------------------------------------------------------- /public/image/theme/star/logo/icon-only.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/public/image/theme/star/logo/icon-only.png -------------------------------------------------------------------------------- /public/image/theme/violet/preview@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/public/image/theme/violet/preview@2x.png -------------------------------------------------------------------------------- /src/plugin/api/index.js: -------------------------------------------------------------------------------- 1 | import api from '@/api' 2 | 3 | export default { 4 | install (Vue) { 5 | Vue.prototype.$api = api 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/plugin/env/index.js: -------------------------------------------------------------------------------- 1 | import env from '@/env' 2 | 3 | export default { 4 | install (Vue) { 5 | Vue.prototype.$env = env 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/views/system/login/image/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/src/views/system/login/image/logo@2x.png -------------------------------------------------------------------------------- /public/image/theme/violet/logo/icon-only.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/public/image/theme/violet/logo/icon-only.png -------------------------------------------------------------------------------- /src/plugin/lodash/index.js: -------------------------------------------------------------------------------- 1 | import lodash from 'lodash' 2 | 3 | export default { 4 | install (Vue) { 5 | Vue.prototype._ = lodash 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/views/system/login/image/login-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/src/views/system/login/image/login-code.png -------------------------------------------------------------------------------- /src/plugin/open/index.js: -------------------------------------------------------------------------------- 1 | import utils from '@/utils' 2 | 3 | export default { 4 | install (Vue) { 5 | Vue.prototype.$open = utils.open 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | }, 5 | rules: { 6 | 'import/no-extraneous-dependencies': 'off' 7 | } 8 | } -------------------------------------------------------------------------------- /public/image/theme/tomorrow-night-blue/logo/all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/public/image/theme/tomorrow-night-blue/logo/all.png -------------------------------------------------------------------------------- /src/filters/modules/time.js: -------------------------------------------------------------------------------- 1 | import utils from '@/utils' 2 | 3 | export default { 4 | timeFormat (...arg) { 5 | return utils.time.format(...arg) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /public/image/theme/tomorrow-night-blue/preview@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/public/image/theme/tomorrow-night-blue/preview@2x.png -------------------------------------------------------------------------------- /public/image/theme/tomorrow-night-blue/logo/icon-only.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/public/image/theme/tomorrow-night-blue/logo/icon-only.png -------------------------------------------------------------------------------- /src/components/d2-fork-me-on-github/image/darkblue@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/src/components/d2-fork-me-on-github/image/darkblue@2x.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ], 5 | // 允许两种编码引入方式共存,也就是 common 规范与 es6 规范的共存处理 6 | sourceType: 'unambiguous' 7 | } 8 | -------------------------------------------------------------------------------- /src/components/d2-icon/font-awesome-4.7.0/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/src/components/d2-icon/font-awesome-4.7.0/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # 忽略目录 2 | build/ 3 | tests/ 4 | node_modules/ 5 | 6 | # node 覆盖率文件 7 | coverage/ 8 | 9 | # 忽略文件 10 | **/*-min.js 11 | **/*.min.js 12 | **/snippets.js 13 | **/snippets.json 14 | -------------------------------------------------------------------------------- /src/components/d2-icon/font-awesome-4.7.0/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/src/components/d2-icon/font-awesome-4.7.0/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /src/components/d2-icon/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/src/components/d2-icon/font-awesome-4.7.0/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /src/components/d2-icon/font-awesome-4.7.0/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/src/components/d2-icon/font-awesome-4.7.0/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/components/d2-vnode/component.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'd2-vnode', 3 | functional: true, 4 | props: { 5 | render: Function 6 | }, 7 | render: (h, ctx) => ctx.props.render(h) 8 | } 9 | -------------------------------------------------------------------------------- /src/components/d2-icon/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huguodong/d2admin-permission/HEAD/src/components/d2-icon/font-awesome-4.7.0/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /src/views/system/function/refresh/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | beforeRouteEnter (to, from, next) { 3 | next(instance => instance.$router.replace(from.fullPath)) 4 | }, 5 | render: h => h() 6 | } 7 | -------------------------------------------------------------------------------- /.env.netlify: -------------------------------------------------------------------------------- 1 | # 构建预览页面 2 | 3 | # 指定构建模式 4 | NODE_ENV=production 5 | 6 | # 标记当前构建方式 7 | VUE_APP_BUILD_MODE=PREVIEW 8 | 9 | # 显示源码按钮 10 | VUE_APP_SCOURCE_LINK=TRUE 11 | 12 | # 部署路径 13 | VUE_APP_PUBLIC_PATH=/ 14 | -------------------------------------------------------------------------------- /src/assets/style/fixed/n-progress.scss: -------------------------------------------------------------------------------- 1 | #nprogress { 2 | .bar { 3 | background: $color-primary !important; 4 | } 5 | .peg { 6 | box-shadow: 0 0 10px $color-primary, 0 0 5px $color-primary !important; 7 | } 8 | } -------------------------------------------------------------------------------- /src/views/system/function/redirect/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | beforeRouteEnter (to, from, next) { 3 | next(instance => instance.$router.replace(JSON.parse(from.params.route))) 4 | }, 5 | render: h => h() 6 | } 7 | -------------------------------------------------------------------------------- /.env.github: -------------------------------------------------------------------------------- 1 | # 构建预览页面 2 | 3 | # 指定构建模式 4 | NODE_ENV=production 5 | 6 | # 标记当前构建方式yin 7 | VUE_APP_BUILD_MODE=PREVIEW 8 | 9 | # 显示源码按钮 10 | VUE_APP_SCOURCE_LINK=TRUE 11 | 12 | # 部署路径 13 | VUE_APP_PUBLIC_PATH=/d2-admin-xiya-go-cms/ 14 | -------------------------------------------------------------------------------- /.env.preview: -------------------------------------------------------------------------------- 1 | # 构建预览页面 2 | 3 | # 指定构建模式 4 | NODE_ENV=production 5 | 6 | # 标记当前构建预览站点 7 | VUE_APP_BUILD_MODE=PREVIEW 8 | 9 | # 显示源码按钮 10 | VUE_APP_SCOURCE_LINK=TRUE 11 | 12 | # 部署路径 13 | VUE_APP_PUBLIC_PATH=/d2-admin-xiya-go-cms/preview/ 14 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "allowSyntheticDefaultImports": false, 5 | "baseUrl": "./", 6 | "paths": { 7 | "@/*": ["src/*"] 8 | } 9 | }, 10 | "exclude": ["node_modules", "dist"] 11 | } 12 | -------------------------------------------------------------------------------- /src/assets/style/theme/violet/index.scss: -------------------------------------------------------------------------------- 1 | @import './setting.scss'; 2 | @import '../theme.scss'; 3 | 4 | .theme-#{$theme-name} { 5 | .d2-layout-header-aside-group { 6 | background: #bc00e3; 7 | background: linear-gradient(120deg, #bc00e3 0%, #4EFFFB 100%); 8 | } 9 | } -------------------------------------------------------------------------------- /src/mixins/el-field-change.js: -------------------------------------------------------------------------------- 1 | export default { 2 | inject: { 3 | elFormItem: { 4 | default: () => ({ onFieldChange () {} }) 5 | } 6 | }, 7 | methods: { 8 | fieldChange () { 9 | this.elFormItem.onFieldChange() 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/views/management/user/style.scss: -------------------------------------------------------------------------------- 1 | .page-management-user { 2 | .container { 3 | height: 100%; 4 | .el-aside { 5 | padding: 10px; 6 | border-right: 1px solid #cfd7e5; 7 | } 8 | .el-main { 9 | padding: 0px; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/d2-bar-space/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "d2-bar-space": { 3 | "scope": "vue,vue-html", 4 | "prefix": "component d2-bar-space", 5 | "body": [ 6 | "" 7 | ], 8 | "description": "# D2Admin 内置组件\n# 工具栏组件 间距块 " 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/snippets/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "utils": { 3 | "scope": "javascript,typescript,vue,vue-html", 4 | "prefix": "utils", 5 | "body": [ 6 | "import utils from '@/utils'" 7 | ], 8 | "description": "# D2Admin 内置工具包 | 导入工具包\n# 文档 https://d2.pub/zh/doc/d2-admin/util" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/assets/svg-icons/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | const requireAll = requireContext => requireContext.keys().map(requireContext) 4 | const req = require.context('./icons', false, /\.svg$/) 5 | const iconMap = requireAll(req) 6 | 7 | Vue.prototype.$IconSvg = iconMap.map(e => e.default.id.slice(3)) 8 | -------------------------------------------------------------------------------- /src/locales/zh-chs.json: -------------------------------------------------------------------------------- 1 | { 2 | "_element": "zh-CN", 3 | "_name": "简体中文", 4 | "page": { 5 | "demo": { 6 | "playground": { 7 | "locales": { 8 | "text": "D2Admin 是一个完全 开源免费 的企业中后台产品前端集成方案,使用最新的前端技术栈,已经做好大部分项目前期准备工作,并且带有大量示例代码,助力管理系统敏捷开发。" 9 | } 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/locales/zh-cht.json: -------------------------------------------------------------------------------- 1 | { 2 | "_element": "zh-TW", 3 | "_name": "繁體中文", 4 | "page": { 5 | "demo": { 6 | "playground": { 7 | "locales": { 8 | "text": "D2Admin 是一個完全 開源免費 的企業中後台產品前端集成方案,使用最新的前端技術棧,已經做好大部分項目前期準備工作,並且帶有大量示例代碼,助力管理系統敏捷開發。" 9 | } 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/d2-time-relative/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "d2-time-relative": { 3 | "scope": "vue,vue-html", 4 | "prefix": "component d2-time-relative", 5 | "body": [ 6 | "" 7 | ], 8 | "description": "# D2Admin 内置组件\n# 时间差显示 " 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/d2-fork-me-on-github/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "d2-fork-me-on-github": { 3 | "scope": "vue,vue-html", 4 | "prefix": "component d2-fork-me-on-github", 5 | "body": [ 6 | "" 7 | ], 8 | "description": "# D2Admin 内置组件\n# Github 地址徽标 " 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /src/components/d2-bar-cell/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "d2-bar-cell": { 3 | "scope": "vue,vue-html", 4 | "prefix": "component d2-bar-cell", 5 | "body": [ 6 | "", 7 | " ${1:content}", 8 | "" 9 | ], 10 | "description": "# D2Admin 内置组件\n# 工具栏组件 容器块 " 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/d2-icon/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "d2-icon": { 3 | "scope": "vue,vue-html", 4 | "prefix": "component d2-icon", 5 | "body": [ 6 | "" 7 | ], 8 | "description": "# D2Admin 内置组件\n# 图标 \n# 文档 https://d2.pub/zh/doc/d2-admin/component/icon.html" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/assets/style/fixed/base.scss: -------------------------------------------------------------------------------- 1 | // 优化显示 2 | html, body { 3 | margin: 0px; 4 | height: 100%; 5 | font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif; 6 | #app { 7 | @extend %full; 8 | a { 9 | text-decoration: none; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/components/d2-vnode/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "d2-vnode": { 3 | "scope": "vue,vue-html", 4 | "prefix": "component d2-vnode", 5 | "body": [ 6 | " h()}\"/>" 7 | ], 8 | "description": "# D2Admin 内置组件\n# VNode 渲染 \n# 参考 https://cn.vuejs.org/v2/guide/render-function.html" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/d2-export-table/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "d2-export-table": { 3 | "scope": "vue,vue-html", 4 | "prefix": "component d2-export-table", 5 | "body": [ 6 | "", 7 | " ${1:content}", 8 | "" 9 | ], 10 | "description": "# D2Admin 内置组件\n# 表格数据导出 " 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/d2-icon-svg/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "d2-icon-svg": { 3 | "scope": "vue,vue-html", 4 | "prefix": "component d2-icon-svg", 5 | "body": [ 6 | "" 7 | ], 8 | "description": "# D2Admin 内置组件\n# SVG 图标 \n# 文档 https://d2.pub/zh/doc/d2-admin/component/icon-svg.html" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/locales/ja.json: -------------------------------------------------------------------------------- 1 | { 2 | "_element": "ja", 3 | "_name": "日本語", 4 | "page": { 5 | "demo": { 6 | "playground": { 7 | "locales": { 8 | "text": "D2Adminは、最新のフロントエンドテクノロジースタックを使用した、完全にオープンソースの無料エンタープライズバックエンド製品フロントエンド統合ソリューションであり、プロジェクトのほとんどの準備を整えており、システムのアジャイル開発の管理に役立つ多くのサンプルコードを備えています。" 9 | } 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/snippets/fn-snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "utils fn getFromMulti": { 3 | "scope": "javascript,typescript,vue,vue-html", 4 | "prefix": "utils fn getFromMulti", 5 | "body": [ 6 | "utils.fn.getFromMulti($1, [ '${2:keyName}' ], ${3:''})" 7 | ], 8 | "description": "# D2Admin 内置工具包 | utils.fn\n# 尝试在一个数据上取值 可以有多个备选 keyName" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/assets/style/theme/register.scss: -------------------------------------------------------------------------------- 1 | @import '~@/assets/style/theme/theme-base.scss'; 2 | 3 | @import '~@/assets/style/theme/d2/index.scss'; 4 | @import '~@/assets/style/theme/line/index.scss'; 5 | @import '~@/assets/style/theme/star/index.scss'; 6 | @import '~@/assets/style/theme/tomorrow-night-blue/index.scss'; 7 | @import '~@/assets/style/theme/violet/index.scss'; -------------------------------------------------------------------------------- /src/store/modules/d2admin/index.js: -------------------------------------------------------------------------------- 1 | export default context => { 2 | const files = require.context('./modules', false, /\.js$/) 3 | const modules = {} 4 | 5 | files.keys().forEach(key => { 6 | modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default(context) 7 | }) 8 | return { 9 | namespaced: true, 10 | modules 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/d2-bar/component.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'd2-bar', 3 | render (createElement) { 4 | return createElement('div', { 5 | style: { 6 | margin: '0 -5px' 7 | }, 8 | attrs: { 9 | class: 'd2-bar', 10 | flex: 'cross:center' 11 | } 12 | }, [ 13 | this.$slots.default 14 | ]) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/filters/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | const files = require.context('./modules', false, /\.js$/) 4 | files.keys().forEach(key => { 5 | const filterModule = files(key).default 6 | for (const filterName in filterModule) { 7 | if (filterModule.hasOwnProperty(filterName)) { 8 | Vue.filter(filterName, filterModule[filterName]) 9 | } 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /src/components/d2-bar-cell/component.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'd2-bar-cell', 3 | render (createElement) { 4 | return createElement('div', { 5 | style: { 6 | margin: '0 5px' 7 | }, 8 | attrs: { 9 | 'class': 'd2-bar-cell', 10 | 'flex-box': '0' 11 | } 12 | }, [ 13 | this.$slots.default 14 | ]) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/snippets/dayjs-snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "utils dayjs": { 3 | "scope": "javascript,typescript,vue,vue-html", 4 | "prefix": "utils dayjs", 5 | "body": [ 6 | "utils.time.dayjs" 7 | ], 8 | "description": "# D2Admin 内置工具包 | utils.time.dayjs\n# 同 https://github.com/iamkun/dayjs\n# 文档 https://github.com/iamkun/dayjs/blob/dev/docs/zh-cn/API-reference.md" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/d2-tencent-captcha/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "d2-tencent-captcha": { 3 | "scope": "vue,vue-html", 4 | "prefix": "component d2-tencent-captcha", 5 | "body": [ 6 | "", 7 | " ${1:content}", 8 | "" 9 | ], 10 | "description": "# D2Admin 内置组件\n# 腾讯防水墙验证码触发器 \n# 参考 https://007.qq.com" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/layout/header-aside/components/header-base-url/index.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /src/components/d2-api-base-url-controller/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "d2-api-base-url-controller": { 3 | "scope": "vue,vue-html", 4 | "prefix": "component d2-api-base-url-controller", 5 | "body": [ 6 | "", 7 | " ${1:content}", 8 | "" 9 | ], 10 | "description": "# D2Admin 内置组件\n# 接口地址切换器 " 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/layout/header-aside/components/header-search/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | -------------------------------------------------------------------------------- /src/components/d2-icon/component.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 18 | -------------------------------------------------------------------------------- /src/store/modules/d2admin/modules/ua.js: -------------------------------------------------------------------------------- 1 | import UaParser from 'ua-parser-js' 2 | 3 | export default context => ({ 4 | namespaced: true, 5 | state: { 6 | // 用户 UA 7 | data: {} 8 | }, 9 | mutations: { 10 | /** 11 | * @description 记录 UA 12 | * @param {Object} state state 13 | */ 14 | get (state) { 15 | state.data = new UaParser().getResult() 16 | } 17 | } 18 | }) 19 | -------------------------------------------------------------------------------- /src/components/d2-dict/component.js: -------------------------------------------------------------------------------- 1 | import dict from '@/mixins/component-dict' 2 | 3 | export default { 4 | name: 'd2-dict', 5 | mixins: [ dict ], 6 | props: { 7 | // 标签名 8 | tag: { 9 | type: String, 10 | default: 'span', 11 | required: false 12 | } 13 | }, 14 | render () { 15 | return { this.currentLabel } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import generatorD2Admin from './modules/d2admin' 4 | import api from '@/api' 5 | import env from '@/env' 6 | import permission from '@/permission' 7 | 8 | Vue.use(Vuex) 9 | 10 | export default new Vuex.Store({ 11 | modules: { 12 | d2admin: generatorD2Admin({ 13 | api, 14 | env, 15 | permission 16 | }) 17 | } 18 | }) 19 | -------------------------------------------------------------------------------- /src/components/d2-icon-svg/component.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | -------------------------------------------------------------------------------- /public/image/loading/loading-spin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/d2-bar-space/component.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'd2-bar-space', 3 | props: { 4 | width: { 5 | type: String, 6 | default: '' 7 | } 8 | }, 9 | render (createElement) { 10 | return createElement('div', { 11 | style: { 12 | maxWidth: this.width 13 | }, 14 | // 普通的 HTML 特性 15 | attrs: { 16 | 'class': 'd2-bar-space', 17 | 'flex-box': '1' 18 | } 19 | }, []) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "_element": "en", 3 | "_name": "English", 4 | "page": { 5 | "demo": { 6 | "playground": { 7 | "locales": { 8 | "text": "D2Admin is a fully open source and free enterprise back-end product front-end integration solution, using the latest front-end technology stack, has prepared most of the project preparations, and with a lot of sample code to help the management system agile development." 9 | } 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/api/modules/captcha.js: -------------------------------------------------------------------------------- 1 | export default ({ request }) => ({ 2 | /** 3 | * @description 验证验证码 4 | * @param {Object} data {String} randstr 腾讯验证返回的 randstr 5 | * @param {Object} data {String} ticket 腾讯验证返回的 ticket 6 | */ 7 | CAPTCHA_CHECK ({ 8 | randstr = '', 9 | ticket = '' 10 | }) { 11 | return request({ 12 | url: '/api/captcha/check', 13 | method: 'post', 14 | data: { 15 | randstr, 16 | ticket 17 | } 18 | }) 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /src/layout/header-aside/components/mixin/menu.js: -------------------------------------------------------------------------------- 1 | import utils from '@/utils' 2 | 3 | export default { 4 | methods: { 5 | handleMenuSelect (index, indexPath) { 6 | if (/^d2-menu-empty-\d+$/.test(index) || index === undefined) { 7 | this.$message.warning('临时菜单') 8 | } else if (/^https:\/\/|http:\/\//.test(index)) { 9 | utils.open(index) 10 | } else { 11 | this.$router.push({ 12 | path: index 13 | }) 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/plugin/permission/index.js: -------------------------------------------------------------------------------- 1 | import permission from '@/permission' 2 | 3 | export default { 4 | install (Vue) { 5 | // v-permission="" 6 | Vue.directive('permission', { 7 | inserted (el, binding) { 8 | const { modifiers, value } = binding 9 | const has = permission(value, modifiers) 10 | if (!has) el.parentNode && el.parentNode.removeChild(el) 11 | } 12 | }) 13 | // this.$permission() 14 | Vue.prototype.$permission = permission 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | import { request } from './service' 2 | 3 | const files = require.context('./modules', false, /\.js$/) 4 | const apiGenerators = files.keys().map(key => files(key).default) 5 | 6 | let api = {} 7 | apiGenerators.forEach(generator => { 8 | const apiInstance = generator({ 9 | request 10 | }) 11 | for (const apiName in apiInstance) { 12 | if (apiInstance.hasOwnProperty(apiName)) { 13 | api[apiName] = apiInstance[apiName] 14 | } 15 | } 16 | }) 17 | 18 | export default api 19 | -------------------------------------------------------------------------------- /src/assets/style/unit/color.scss: -------------------------------------------------------------------------------- 1 | // 主色 2 | $color-primary: #409EFF; 3 | 4 | // 辅助色 5 | $color-info: #909399; 6 | $color-success: #67C23A; 7 | $color-warning: #E6A23C; 8 | $color-danger: #F56C6C; 9 | 10 | // 文字 11 | $color-text-main: #303133; 12 | $color-text-normal: #606266; 13 | $color-text-sub: #909399; 14 | $color-text-placehoder: #C0C4CC; 15 | 16 | // 边框 17 | $color-border-1: #DCDFE6; 18 | $color-border-2: #E4E7ED; 19 | $color-border-3: #EBEEF5; 20 | $color-border-4: #F2F6FC; 21 | 22 | // 背景 23 | $color-bg: #f8f8f9; -------------------------------------------------------------------------------- /src/snippets/log-snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "scope": "javascript,typescript,vue,vue-html", 4 | "prefix": "log", 5 | "body": [ 6 | "console.log(${1:true})" 7 | ], 8 | "description": "# D2Admin 内置代码片段\n# 控制台输出日志" 9 | }, 10 | "log with title": { 11 | "scope": "javascript,typescript,vue,vue-html", 12 | "prefix": "log group", 13 | "body": [ 14 | "console.group('${1:group name}')", 15 | "console.log(${2:true})", 16 | "console.groupEnd()" 17 | ], 18 | "description": "# D2Admin 内置代码片段\n# 控制台输出日志组" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/d2-dict-select/component.js: -------------------------------------------------------------------------------- 1 | import dict from '@/mixins/component-dict' 2 | 3 | export default { 4 | name: 'd2-dict-select', 5 | mixins: [ dict ], 6 | render () { 7 | const component = 8 | 13 | { this.options.map(item => ) } 14 | 15 | return component 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/store/modules/d2admin/modules/releases.js: -------------------------------------------------------------------------------- 1 | import utils from '@/utils' 2 | 3 | export default context => ({ 4 | namespaced: true, 5 | mutations: { 6 | /** 7 | * @description 显示版本信息 8 | * @param {Object} state state 9 | */ 10 | versionShow () { 11 | utils.log.capsule('D2Admin', `v${context.env.VUE_APP_VERSION}`) 12 | console.log('D2 Admin https://github.com/d2-projects/d2-admin') 13 | console.log('Document https://d2.pub/zh/doc/d2-admin') 14 | console.log('请不要吝啬您的 star,谢谢 ~') 15 | } 16 | } 17 | }) 18 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import d2Container from './d2-container' 3 | 4 | Vue.component('d2-container', d2Container) 5 | 6 | const vueFiles = require.context('./', true, /component\.vue$/) 7 | vueFiles.keys().forEach(key => { 8 | const component = vueFiles(key).default 9 | Vue.component(component.name, component) 10 | }) 11 | 12 | const jsFiles = require.context('./', true, /component\.js$/) 13 | jsFiles.keys().forEach(key => { 14 | const component = jsFiles(key).default 15 | Vue.component(component.name, component) 16 | }) 17 | -------------------------------------------------------------------------------- /src/store/modules/d2admin/modules/gray.js: -------------------------------------------------------------------------------- 1 | export default context => ({ 2 | namespaced: true, 3 | state: { 4 | // 灰度 5 | active: false 6 | }, 7 | mutations: { 8 | /** 9 | * @description 切换灰度状态 10 | * @param {Object} state state 11 | */ 12 | toggle (state) { 13 | state.active = !state.active 14 | }, 15 | /** 16 | * @description 设置灰度模式 17 | * @param {Object} state state 18 | * @param {Boolean} active active 19 | */ 20 | set (state, active) { 21 | state.active = active 22 | } 23 | } 24 | }) 25 | -------------------------------------------------------------------------------- /src/views/system/error/404/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 25 | 26 | 29 | -------------------------------------------------------------------------------- /src/assets/style/animate/vue-transition.scss: -------------------------------------------------------------------------------- 1 | // 过渡动画 横向渐变 2 | .fade-transverse-leave-active, 3 | .fade-transverse-enter-active { 4 | transition: all .5s; 5 | } 6 | .fade-transverse-enter { 7 | opacity: 0; 8 | transform: translateX(-30px); 9 | } 10 | .fade-transverse-leave-to { 11 | opacity: 0; 12 | transform: translateX(30px); 13 | } 14 | 15 | // 过渡动画 缩放渐变 16 | .fade-scale-leave-active, 17 | .fade-scale-enter-active { 18 | transition: all .3s; 19 | } 20 | .fade-scale-enter { 21 | opacity: 0; 22 | transform: scale(1.2); 23 | } 24 | .fade-scale-leave-to { 25 | opacity: 0; 26 | transform: scale(0.8); 27 | } 28 | -------------------------------------------------------------------------------- /src/components/d2-export-table/component.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 32 | -------------------------------------------------------------------------------- /src/env.js: -------------------------------------------------------------------------------- 1 | import { cloneDeep } from 'lodash' 2 | 3 | const env = cloneDeep(process.env) 4 | 5 | let newEnv = {} 6 | 7 | Object.keys(env).forEach(keyname => { 8 | if (/^.+\|(number|string)$/.test(env[keyname])) { 9 | const [ value, type ] = env[keyname].split('|') 10 | switch (type) { 11 | case 'number': newEnv[keyname] = Number(value); break 12 | case 'string': 13 | if (value === 'empty') newEnv[keyname] = '' 14 | else newEnv[keyname] = String(value) 15 | break 16 | default: break 17 | } 18 | } else { 19 | newEnv[keyname] = env[keyname] 20 | } 21 | }) 22 | 23 | export default newEnv 24 | -------------------------------------------------------------------------------- /.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 | overrides: [ 18 | { 19 | files: [ 20 | '**/__tests__/*.{j,t}s?(x)', 21 | '**/tests/unit/**/*.spec.{j,t}s?(x)' 22 | ], 23 | env: { 24 | jest: true 25 | } 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /src/views/system/index/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 27 | -------------------------------------------------------------------------------- /src/permission.js: -------------------------------------------------------------------------------- 1 | import { isArray, isString } from 'lodash' 2 | import utils from '@/utils' 3 | import store from '@/store' 4 | 5 | /** 6 | * @description 权限检查 7 | * @param {String|Array} value 需要的权限 8 | * @param {Object}} config {Boolean} all 全部匹配 9 | * @param {Object}} config {Boolean} not 取反 10 | */ 11 | export default function permission (value = '', { all = false, not = false } = {}) { 12 | if (isArray(value) || isString(value)) { 13 | const permissions = store.state.d2admin.permission.permissions 14 | let has = utils.helper[all ? 'allIn' : 'oneOf'](permissions, value) 15 | if (not) has = !has 16 | return has 17 | } else { 18 | return false 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/plugin/log/index.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | import utils from '@/utils' 3 | 4 | export default { 5 | install (Vue) { 6 | // 快速打印 log 7 | Vue.prototype.$log = { 8 | ...utils.log, 9 | push (data) { 10 | if (typeof data === 'string') { 11 | // 如果传递来的数据是字符串 12 | // 赋值给 message 字段 13 | // 为了方便使用 14 | // eg: this.$log.push('foo text') 15 | store.dispatch('d2admin/log/push', { 16 | message: data 17 | }) 18 | } else if (typeof data === 'object') { 19 | // 如果传递来的数据是对象 20 | store.dispatch('d2admin/log/push', data) 21 | } 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/d2-bar/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "d2-bar": { 3 | "scope": "vue,vue-html", 4 | "prefix": "component d2-bar", 5 | "body": [ 6 | "", 7 | " ${1:content}", 8 | "" 9 | ], 10 | "description": "# D2Admin 内置组件\n# 工具栏组件 " 11 | }, 12 | "d2-bar + cell + space": { 13 | "scope": "vue,vue-html", 14 | "prefix": "component d2-bar + cell + space", 15 | "body": [ 16 | "", 17 | " ", 18 | " content", 19 | " ", 20 | " ", 21 | " ", 22 | " content", 23 | " ", 24 | "" 25 | ], 26 | "description": "# D2Admin 内置组件\n# 工具栏组件套 " 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/d2-fork-me-on-github/component.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 20 | 21 | 26 | -------------------------------------------------------------------------------- /src/components/d2-container/components/d2-container-ghost-bs.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 26 | -------------------------------------------------------------------------------- /src/locales/mixin.js: -------------------------------------------------------------------------------- 1 | export default { 2 | methods: { 3 | onChangeLocale (command) { 4 | this.$i18n.locale = command 5 | let message = `当前语言:${this.$t('_name')} [ ${this.$i18n.locale} ]` 6 | if (this.$env.VUE_APP_BUILD_MODE === 'PREVIEW') { 7 | message = [ 8 | `当前语言:${this.$t('_name')} [ ${this.$i18n.locale} ]`, 9 | `仅提供切换功能,没有配置具体的语言数据 `, 10 | `文档参考:《国际化 | D2Admin》` 11 | ].join('
') 12 | } 13 | this.$notify({ 14 | title: '语言变更', 15 | dangerouslyUseHTMLString: true, 16 | message 17 | }) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/mixins/crud-permission.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data () { 3 | return { 4 | // 默认有四个权限设置 在页面中可以覆盖 5 | permission: { 6 | query: '', 7 | add: '', 8 | edit: '', 9 | remove: '' 10 | } 11 | } 12 | }, 13 | methods: { 14 | /** 15 | * @description 没有设置权限或者具有权限 返回 true 16 | * @description 设置了权限但是没有权限 返回 false 17 | * @param {String} name 权限名称 18 | * @param {Boolean} trueReturn 有权限时返回的值 19 | * @param {Boolean} falseReturn 没有权限时返回的值 20 | */ 21 | p (name, trueReturn = true, falseReturn = false) { 22 | if (this.permission[name] && !this.$permission(this.permission[name])) return falseReturn 23 | return trueReturn 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/layout/header-aside/components/header-fullscreen/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 28 | -------------------------------------------------------------------------------- /src/components/d2-pagination/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "d2-pagination": { 3 | "scope": "vue,vue-html", 4 | "prefix": "component d2-pagination", 5 | "body": [ 6 | "" 7 | ], 8 | "description": "# D2Admin 内置组件\n# 分页封装 \n# 参考 https://element.eleme.cn/#/zh-CN/component/pagination" 9 | }, 10 | "d2-pagination footer": { 11 | "scope": "vue,vue-html", 12 | "prefix": "component d2-pagination footer", 13 | "body": [ 14 | "" 15 | ], 16 | "description": "# D2Admin 内置组件\n# 分页封装 footer 位置\n# 参考 https://element.eleme.cn/#/zh-CN/component/pagination" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/mixins/crud-form-helper.js: -------------------------------------------------------------------------------- 1 | export default { 2 | methods: { 3 | /** 4 | * @description 根据不同的模式 返回不同的值 5 | * @param {*} createModeValve 新增模式返回值 6 | * @param {*} editModeValue 编辑模式返回值 7 | * @param {*} defaultValue 没有匹配的模式返回值 默认为 createModeValve 8 | */ 9 | switchByMode (createModeValve = '', editModeValue = '', defaultValue = createModeValve) { 10 | if (this.mode === 'create') { 11 | return createModeValve 12 | } else if (this.mode === 'edit') { 13 | return editModeValue 14 | } else { 15 | return defaultValue 16 | } 17 | }, 18 | /** 19 | * @description 设置表单模式 20 | * @param {String} mode 模式名称 edit or create 21 | */ 22 | setMode (mode) { this.mode = mode } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/d2-tencent-captcha/component.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 30 | -------------------------------------------------------------------------------- /src/components/d2-dict-radio/component.js: -------------------------------------------------------------------------------- 1 | import dict from '@/mixins/component-dict' 2 | import log from '@/store/modules/d2admin/modules/log' 3 | 4 | export default { 5 | name: 'd2-dict-radio', 6 | mixins: [ dict ], 7 | props: { 8 | button: { type: Boolean, default: false, required: false } 9 | }, 10 | render () { 11 | const tagName = this.button ? 'el-radio-button' : 'el-radio' 12 | const component = 13 | 14 | { 15 | 16 | this.options.map( 17 | item => 18 | 19 | { item.label } 20 | 21 | ) 22 | } 23 | 24 | return component 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/mixins/component-vmodel.js: -------------------------------------------------------------------------------- 1 | import fieldChange from './el-field-change' 2 | 3 | export const vModelString = { 4 | mixins: [ 5 | fieldChange 6 | ], 7 | props: { 8 | value: { 9 | type: String, 10 | default: '', 11 | required: false 12 | } 13 | }, 14 | data () { 15 | return { 16 | currentValue: '' 17 | } 18 | }, 19 | watch: { 20 | value: { 21 | handler () { 22 | this.vModelMixinResetCurrentValue() 23 | }, 24 | immediate: true 25 | } 26 | }, 27 | methods: { 28 | vModelMixinResetCurrentValue () { 29 | this.currentValue = this.value 30 | }, 31 | vModelMixinEmit () { 32 | this.$emit('input', this.currentValue) 33 | this.$emit('change', this.currentValue) 34 | this.fieldChange() 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/components/d2-container/components/d2-container-full-bs.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 30 | -------------------------------------------------------------------------------- /src/components/d2-container/components/d2-container-ghost.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 32 | -------------------------------------------------------------------------------- /src/components/d2-table/snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "d2-table": { 3 | "scope": "vue,vue-html", 4 | "prefix": "component d2-table", 5 | "body": [ 6 | "" 7 | ], 8 | "description": "# D2Admin 内置组件\n# 配置式表格 " 9 | }, 10 | "d2-table data": { 11 | "scope": "javascript,typescript", 12 | "prefix": "component d2-table data", 13 | "body": [ 14 | "table: {", 15 | " data: [],", 16 | " columns: [", 17 | " {", 18 | " prop: 'id',", 19 | " label: 'ID'", 20 | " },", 21 | " {", 22 | " prop: 'id',", 23 | " label: 'ID',", 24 | " render: ({ row, column, index }) =>
{ row.id }
", 25 | " }", 26 | " ]", 27 | "}" 28 | ], 29 | "description": "# D2Admin 内置组件\n# 配置式表格 数据模板 " 30 | } 31 | } 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/components/d2-scrollbar/component.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | 20 | 35 | -------------------------------------------------------------------------------- /src/components/d2-card/component.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 36 | -------------------------------------------------------------------------------- /src/layout/header-aside/components/header-user/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 34 | -------------------------------------------------------------------------------- /src/utils/modules/time.js: -------------------------------------------------------------------------------- 1 | import Dayjs from 'dayjs' 2 | import zh from 'dayjs/locale/zh-cn' 3 | import LocalizedFormat from 'dayjs/plugin/localizedFormat' 4 | import relativeTime from 'dayjs/plugin/relativeTime' 5 | const customizedLocale = { 6 | ...zh, 7 | meridiem: (hour, minute, isLowercase) => hour > 12 ? '下午' : '上午' 8 | } 9 | Dayjs.locale(customizedLocale) 10 | Dayjs.extend(LocalizedFormat) 11 | Dayjs.extend(relativeTime) 12 | 13 | export const dayjs = Dayjs 14 | 15 | export function format (value, config = 'YYYY年M月D日') { 16 | let currentValue = value 17 | if (!dayjs.isDayjs(value)) { 18 | if (dayjs(value).isValid()) { 19 | currentValue = dayjs(value) 20 | } else { 21 | currentValue = { 22 | format () { 23 | return '' 24 | } 25 | } 26 | } 27 | } 28 | return currentValue.format(config) 29 | } 30 | -------------------------------------------------------------------------------- /src/store/modules/d2admin/modules/sys.js: -------------------------------------------------------------------------------- 1 | export default context => ({ 2 | namespaced: true, 3 | actions: { 4 | /** 5 | * @description 从持久化数据加载一系列的设置 6 | * @param {Object} vuex context 7 | */ 8 | async load ({ dispatch }) { 9 | // 加载用户名 10 | await dispatch('d2admin/user/load', undefined, { root: true }) 11 | // 加载主题 12 | await dispatch('d2admin/theme/load', undefined, { root: true }) 13 | // 加载页面过渡效果设置 14 | await dispatch('d2admin/transition/load', undefined, { root: true }) 15 | // 加载上次退出时的多页列表 16 | await dispatch('d2admin/page/openedLoad', undefined, { root: true }) 17 | // 加载侧边栏折叠状态 18 | await dispatch('d2admin/menu/asideCollapseLoad', undefined, { root: true }) 19 | // 加载全局尺寸 20 | await dispatch('d2admin/size/load', undefined, { root: true }) 21 | } 22 | } 23 | }) 24 | -------------------------------------------------------------------------------- /src/assets/style/public.scss: -------------------------------------------------------------------------------- 1 | @import '~@/assets/style/unit/color.scss'; 2 | 3 | // 工具类名统一前缀 4 | $prefix: d2; 5 | 6 | // 禁止用户选中 鼠标变为手形 7 | %unable-select { 8 | user-select: none; 9 | cursor: pointer; 10 | } 11 | 12 | // 填满父元素 13 | // 组要父元素 position: relative | absolute; 14 | %full { 15 | position: absolute; 16 | top: 0px; 17 | right: 0px; 18 | bottom: 0px; 19 | left: 0px; 20 | } 21 | 22 | // flex 垂直水平居中 23 | %flex-center-row { 24 | display: flex; 25 | justify-content: center; 26 | align-items: center; 27 | flex-direction: row; 28 | } 29 | %flex-center-col { 30 | display: flex; 31 | justify-content: center; 32 | align-items: center; 33 | flex-direction: column; 34 | } 35 | 36 | // 将元素模拟成卡片外观 37 | %card { 38 | border: 1px solid #dddee1; 39 | border-color: #e9eaec; 40 | background: #fff; 41 | border-radius: 4px; 42 | font-size: 14px; 43 | position: relative; 44 | } -------------------------------------------------------------------------------- /src/layout/header-aside/components/header-locales/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 29 | -------------------------------------------------------------------------------- /src/components/d2-time-relative/component.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 44 | -------------------------------------------------------------------------------- /src/store/modules/d2admin/modules/fullscreen.js: -------------------------------------------------------------------------------- 1 | import screenfull from 'screenfull' 2 | 3 | export default context => ({ 4 | namespaced: true, 5 | state: { 6 | // 全屏激活 7 | active: false 8 | }, 9 | mutations: { 10 | /** 11 | * @description 初始化监听 12 | * @param {Object} vuex context 13 | */ 14 | listen (state) { 15 | if (screenfull.enabled) { 16 | screenfull.on('change', () => { 17 | if (!screenfull.isFullscreen) { 18 | state.active = false 19 | } 20 | }) 21 | } 22 | }, 23 | /** 24 | * @description 切换全屏 25 | * @param {Object} vuex context 26 | */ 27 | toggle (state) { 28 | if (screenfull.isFullscreen) { 29 | screenfull.exit() 30 | state.active = false 31 | } else { 32 | screenfull.request() 33 | state.active = true 34 | } 35 | } 36 | } 37 | }) 38 | -------------------------------------------------------------------------------- /src/components/d2-pagination/component.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 42 | -------------------------------------------------------------------------------- /src/components/d2-container/components/d2-container-card-bs.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 32 | -------------------------------------------------------------------------------- /src/components/d2-container/components/d2-container-full.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 37 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import i18n from './i18n' 3 | import App from './App' 4 | import store from '@/store' 5 | import d2Admin from '@/plugin/d2admin' 6 | 7 | import router, { constantRoutes } from './router' 8 | 9 | import './permission' 10 | 11 | Vue.use(d2Admin) 12 | 13 | new Vue({ 14 | router, 15 | store, 16 | i18n, 17 | render: h => h(App), 18 | async created () { 19 | // 处理路由 得到每一级的路由设置 20 | this.$store.commit('d2admin/page/init', constantRoutes) 21 | // 加载接口配置 22 | await this.$store.dispatch('d2admin/api/load') 23 | }, 24 | async mounted () { 25 | // 展示系统信息 26 | this.$store.commit('d2admin/releases/versionShow') 27 | // 用户登录后从数据库加载一系列的设置 28 | await this.$store.dispatch('d2admin/sys/load') 29 | // 获取并记录用户 UA 30 | this.$store.commit('d2admin/ua/get') 31 | // 初始化全屏监听 32 | this.$store.commit('d2admin/fullscreen/listen') 33 | } 34 | }).$mount('#app') 35 | -------------------------------------------------------------------------------- /src/api/modules/upload.js: -------------------------------------------------------------------------------- 1 | export default ({ request }) => ({ 2 | /** 3 | * @description 上传图片 4 | * @description http://yapi.xiya.vip/project/11/interface/api/235 5 | */ 6 | UPLOAD_IMAGE (image) { 7 | const data = new FormData() 8 | data.append('file', image) 9 | return request({ 10 | url: '/api/upload/image', 11 | method: 'post', 12 | header: { 13 | 'Content-Type': 'multipart/form-data' 14 | }, 15 | data 16 | }) 17 | }, 18 | /** 19 | * @description 上传图片到百度 OOS 20 | * @description http://yapi.xiya.vip/project/11/interface/api/460 21 | */ 22 | UPLOAD_IMAGE_OOS (image) { 23 | const data = new FormData() 24 | data.append('file', image) 25 | return request({ 26 | url: '/api/upload/head_image', 27 | method: 'post', 28 | header: { 29 | 'Content-Type': 'multipart/form-data' 30 | }, 31 | data 32 | }) 33 | } 34 | }) 35 | -------------------------------------------------------------------------------- /src/components/d2-drawer-container/component.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 31 | 32 | 37 | -------------------------------------------------------------------------------- /src/layout/header-aside/components/header-theme/index.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 39 | -------------------------------------------------------------------------------- /src/plugin/error/index.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | import utils from '@/utils' 3 | import env from '@/env' 4 | 5 | export default { 6 | install (Vue) { 7 | Vue.config.errorHandler = function (error, instance, info) { 8 | Vue.nextTick(() => { 9 | // store 追加 log 10 | store.dispatch('d2admin/log/push', { 11 | message: `${info}: ${error.message}`, 12 | type: 'danger', 13 | meta: { 14 | error, 15 | instance 16 | } 17 | }) 18 | // 只在开发模式下打印 log 19 | if (env.NODE_ENV === 'development') { 20 | utils.log.capsule('D2Admin', 'ErrorHandler', 'danger') 21 | utils.log.danger('>>>>>> 错误信息 >>>>>>') 22 | console.log(info) 23 | utils.log.danger('>>>>>> Vue 实例 >>>>>>') 24 | console.log(instance) 25 | utils.log.danger('>>>>>> Error >>>>>>') 26 | console.log(error) 27 | } 28 | }) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/components/d2-container/components/d2-container-card.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 38 | -------------------------------------------------------------------------------- /src/mixins/component-multiple.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | // 多选 4 | multiple: { type: Boolean, default: false, required: false }, 5 | // 是否序列为字符串 6 | stringify: { type: Boolean, default: false, required: false } 7 | }, 8 | methods: { 9 | tryParseMultipleString (value) { 10 | if (this.multiple) { 11 | // 最后总应该返回数组 12 | if (this._.isArray(value)) { 13 | return value 14 | } else if (this.stringify && this._.isString(value)) { 15 | return value.split(',').filter(e => e !== '').map(Number) 16 | } else { 17 | return [ value ] 18 | } 19 | } else { 20 | // 最后总应该返回单个值 21 | if (this._.isArray(value)) { 22 | return value.join(',') 23 | } else { 24 | return value 25 | } 26 | } 27 | }, 28 | tryStringify (value) { 29 | if (this._.isArray(value) && this.multiple && this.stringify) { 30 | return value.join(',') 31 | } else { 32 | return value 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/modules/fn.js: -------------------------------------------------------------------------------- 1 | import shortid from 'shortid' 2 | import { get, cloneDeep } from 'lodash' 3 | 4 | /** 5 | * @description 尝试在一个数据上取值 可以有多个备选 keyName 6 | * @param {Object} parent 被取值的数据 7 | * @param {Array} paths 可供选择的取值路径 8 | * @param {*} defaultValue 默认值 9 | */ 10 | export function getFromMulti (parent = {}, paths = [], defaultValue = '') { 11 | let result = defaultValue 12 | for (let pathIndex = 0; pathIndex < paths.length; pathIndex++) { 13 | const path = paths[pathIndex] 14 | const value = get(parent, path, defaultValue) 15 | if (value !== defaultValue) { 16 | result = value 17 | break 18 | } 19 | } 20 | return result 21 | } 22 | 23 | /** 24 | * @description 给指定的数组添加唯一 id 字段 25 | * @description https://github.com/dylang/shortid 26 | * @param {Array} source 数据源 27 | * @param {String} idKeyName 字段名 28 | */ 29 | export function arrayAddUniqueId (source = [], idKeyName = 'id') { 30 | return source.map(item => { 31 | let currentItem = cloneDeep(item) 32 | currentItem[idKeyName] = shortid.generate() 33 | return currentItem 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/modules/cookies.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | import env from '@/env' 3 | 4 | /** 5 | * @description 存储 cookie 值 6 | * @param {String} name cookie name 7 | * @param {String} value cookie value 8 | * @param {Object} setting cookie setting 9 | */ 10 | export function set (name = 'default', value = '', cookieSetting = {}) { 11 | let currentCookieSetting = { 12 | expires: 1 13 | } 14 | Object.assign(currentCookieSetting, cookieSetting) 15 | Cookies.set(`d2admin-${env.VUE_APP_VERSION}-${name}`, value, currentCookieSetting) 16 | } 17 | 18 | /** 19 | * @description 拿到 cookie 值 20 | * @param {String} name cookie name 21 | */ 22 | export function get (name = 'default') { 23 | return Cookies.get(`d2admin-${env.VUE_APP_VERSION}-${name}`) 24 | } 25 | 26 | /** 27 | * @description 拿到 cookie 全部的值 28 | */ 29 | export function getAll () { 30 | return Cookies.get() 31 | } 32 | 33 | /** 34 | * @description 删除 cookie 35 | * @param {String} name cookie name 36 | */ 37 | export function remove (name = 'default') { 38 | return Cookies.remove(`d2admin-${env.VUE_APP_VERSION}-${name}`) 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 李杨 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 | -------------------------------------------------------------------------------- /src/store/snippets/snippets.js: -------------------------------------------------------------------------------- 1 | exports.default = function () { 2 | let result = {} 3 | 4 | const scope = 'javascript,typescript,vue,vue-html' 5 | 6 | const helpers = [ 7 | 'mapState', 8 | 'mapGetters', 9 | 'mapMutations', 10 | 'mapActions' 11 | ] 12 | 13 | // 在文件中导入 store 14 | result['store import at .js'] = { 15 | scope, 16 | prefix: 'store import', 17 | body: [ 18 | `import store from '@/store'` 19 | ], 20 | description: `# D2Admin 状态管理 | store\n# 在 js 文件中导入 store` 21 | } 22 | 23 | // 在组件中导入全部辅助方法 24 | result['store import all'] = { 25 | scope, 26 | prefix: 'store import all', 27 | body: [ 28 | `import { ${helpers.join(', ')} } from 'vuex'` 29 | ], 30 | description: `# D2Admin 状态管理 | store\n# 在组件中导入 store` 31 | } 32 | 33 | // 在组件中导入某个辅助方法 34 | helpers.forEach(helper => { 35 | result[helper] = { 36 | scope, 37 | prefix: helper, 38 | body: [ 39 | `import { ${helper} } from 'vuex'` 40 | ], 41 | description: `# D2Admin 状态管理 | store\n# 在组件中导入 ${helper} 辅助方法` 42 | } 43 | }) 44 | 45 | return result 46 | } 47 | -------------------------------------------------------------------------------- /src/store/modules/d2admin/modules/transition.js: -------------------------------------------------------------------------------- 1 | // 设置文件 2 | import setting from '@/setting.js' 3 | 4 | export default context => ({ 5 | namespaced: true, 6 | state: { 7 | // 是否开启页面过度动画 8 | active: setting.transition.active 9 | }, 10 | actions: { 11 | /** 12 | * @description 设置开启状态 13 | * @param {Object} vuex context 14 | * @param {Boolean} active 新的状态 15 | */ 16 | async set ({ state, dispatch }, active) { 17 | // store 赋值 18 | state.active = active 19 | // 持久化 20 | await dispatch('d2admin/db/set', { 21 | dbName: 'sys', 22 | path: 'transition.active', 23 | value: state.active, 24 | user: true 25 | }, { root: true }) 26 | }, 27 | /** 28 | * 从数据库读取页面过渡动画设置 29 | * @param {Object} vuex context 30 | */ 31 | async load ({ state, dispatch }) { 32 | // store 赋值 33 | state.active = await dispatch('d2admin/db/get', { 34 | dbName: 'sys', 35 | path: 'transition.active', 36 | defaultValue: setting.transition.active, 37 | user: true 38 | }, { root: true }) 39 | } 40 | } 41 | }) 42 | -------------------------------------------------------------------------------- /src/i18n.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueI18n from 'vue-i18n' 3 | import utils from '@/utils' 4 | import env from '@/env' 5 | 6 | Vue.use(VueI18n) 7 | 8 | function loadLocaleMessages () { 9 | const locales = require.context('./locales', true, /[A-Za-z0-9-_,\s]+\.json$/i) 10 | const messages = {} 11 | for (const key of locales.keys()) { 12 | const matched = key.match(/([A-Za-z0-9-_]+)\./i) 13 | if (matched && matched.length > 1) { 14 | const locale = matched[1] 15 | const localeElementUI = require(`element-ui/lib/locale/lang/${locales(key)._element}`) 16 | messages[locale] = { 17 | ...locales(key), 18 | ...localeElementUI ? localeElementUI.default : {} 19 | } 20 | } 21 | } 22 | return messages 23 | } 24 | 25 | const messages = loadLocaleMessages() 26 | 27 | Vue.prototype.$languages = Object.keys(messages).map(langlage => ({ 28 | label: messages[langlage]._name, 29 | value: langlage 30 | })) 31 | 32 | const i18n = new VueI18n({ 33 | locale: utils.cookies.get('lang') || env.VUE_APP_I18N_LOCALE, 34 | fallbackLocale: env.VUE_APP_I18N_FALLBACK_LOCALE, 35 | messages 36 | }) 37 | 38 | export default i18n 39 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | import env from '@/env' 2 | import * as cookies from './modules/cookies' 3 | import * as db from './modules/db' 4 | import * as fn from './modules/fn' 5 | import * as helper from './modules/helper' 6 | import * as log from './modules/log' 7 | import * as time from './modules/time' 8 | 9 | const utils = { 10 | cookies, 11 | time, 12 | db, 13 | fn, 14 | helper, 15 | log, 16 | import: require('./modules/import-' + env.NODE_ENV) 17 | } 18 | 19 | /** 20 | * @description 更新标题 21 | * @param {String} title 标题 22 | */ 23 | utils.title = function (titleText) { 24 | const processTitle = env.VUE_APP_TITLE || 'D2Admin' 25 | window.document.title = `${processTitle}${titleText ? ` | ${titleText}` : ''}` 26 | } 27 | 28 | /** 29 | * @description 打开新页面 30 | * @param {String} url 地址 31 | */ 32 | utils.open = function (url) { 33 | var a = document.createElement('a') 34 | a.setAttribute('href', url) 35 | a.setAttribute('target', '_blank') 36 | a.setAttribute('id', 'd2admin-link-temp') 37 | document.body.appendChild(a) 38 | a.click() 39 | document.body.removeChild(document.getElementById('d2admin-link-temp')) 40 | } 41 | 42 | export default utils 43 | -------------------------------------------------------------------------------- /src/plugin/d2admin/index.js: -------------------------------------------------------------------------------- 1 | // Element 2 | import ElementUI from 'element-ui' 3 | import 'element-ui/lib/theme-chalk/index.css' 4 | // flex 布局库 5 | import 'flex.css' 6 | // 组件 7 | import '@/components' 8 | // 过滤器 9 | import '@/filters' 10 | // svg 图标 11 | import '@/assets/svg-icons' 12 | // 国际化 13 | import i18n from '@/i18n' 14 | 15 | // 功能插件 16 | import pluginApi from '@/plugin/api' 17 | import pluginEnv from '@/plugin/env' 18 | import pluginError from '@/plugin/error' 19 | import pluginLodash from '@/plugin/lodash' 20 | import pluginLog from '@/plugin/log' 21 | import pluginOpen from '@/plugin/open' 22 | import pluginPermission from '@/plugin/permission' 23 | 24 | export default { 25 | install (Vue) { 26 | // 设置为 false 以阻止 vue 在启动时生成生产提示 27 | // https://cn.vuejs.org/v2/api/#productionTip 28 | Vue.config.productionTip = false 29 | // Element 30 | Vue.use(ElementUI, { 31 | i18n: (key, value) => i18n.t(key, value) 32 | }) 33 | // 插件 34 | Vue.use(pluginApi) 35 | Vue.use(pluginEnv) 36 | Vue.use(pluginError) 37 | Vue.use(pluginLodash) 38 | Vue.use(pluginLog) 39 | Vue.use(pluginOpen) 40 | Vue.use(pluginPermission) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/store/modules/d2admin/snippets/snippets.js: -------------------------------------------------------------------------------- 1 | exports.default = function () { 2 | let result = {} 3 | 4 | const scope = 'javascript,typescript,vue,vue-html' 5 | 6 | const helpers = [ 7 | 'mapState', 8 | 'mapGetters', 9 | 'mapMutations', 10 | 'mapActions' 11 | ] 12 | 13 | const d2adminModules = [ 14 | 'account', 15 | 'fullscreen', 16 | 'page', 17 | 'theme', 18 | 'api', 19 | 'gray', 20 | 'releases', 21 | 'transition', 22 | 'color', 23 | 'log', 24 | 'search', 25 | 'ua', 26 | 'db', 27 | 'dict', 28 | 'menu', 29 | 'size', 30 | 'user' 31 | ] 32 | 33 | // 在组件中导入某个模块 34 | helpers.forEach(helper => { 35 | d2adminModules.forEach(d2adminModule => { 36 | const name = `${helper} d2:${d2adminModule}` 37 | result[name] = { 38 | scope, 39 | prefix: name, 40 | body: [ 41 | `...${helper}('d2admin/${d2adminModule}', [`, 42 | ` '\$\{1:${helper.replace('map', '').toLowerCase()} name\}'`, 43 | `])\$\{2:,\}` 44 | ], 45 | description: `# D2Admin 状态管理 | store\n# 在组件中使用 ${helper} 导入 d2admin ${d2adminModule} 模块` 46 | } 47 | }) 48 | }) 49 | 50 | return result 51 | } 52 | -------------------------------------------------------------------------------- /src/components/d2-throttle/component.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'd2-throttle', 3 | functional: true, 4 | render (createElement, context) { 5 | // [options.leading=true] (boolean): 指定调用在节流开始前 6 | // [options.trailing=true] (boolean): 指定调用在节流结束后 7 | const options = ({ leading = true, trailing = true } = {}) => ({ leading, trailing })(context.props) 8 | const wait = context.props.wait 9 | const vnodeList = context.slots().default 10 | if (vnodeList === undefined) { 11 | console.warn(' 组件必须要有子元素') 12 | return null 13 | } 14 | const vnode = vnodeList[0] || null 15 | if (vnode.tag === 'button') { 16 | const defaultFun = vnode.data.on.click 17 | const throttleFun = this._.throttle(defaultFun, wait, options) 18 | vnode.data.on.click = throttleFun 19 | } else if (vnode.componentOptions && vnode.componentOptions.tag === 'el-button') { 20 | const defaultFun = vnode.componentOptions.listeners.click 21 | const throttleFun = this._.throttle(defaultFun, wait, options) 22 | vnode.componentOptions.listeners.click = throttleFun 23 | } else { 24 | console.warn(' 组件内只能出现下面组件的任意一个且唯一 el-button、button') 25 | return vnode 26 | } 27 | return vnode 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/d2-table-columns-fixed-controller/component.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 20 | 21 | 55 | -------------------------------------------------------------------------------- /tools/dir-exists.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | /** 5 | * 读取路径信息 6 | * @param {string} path 路径 7 | */ 8 | function getStat(path){ 9 | return new Promise((resolve, reject) => { 10 | fs.stat(path, (err, stats) => { 11 | if(err){ 12 | resolve(false) 13 | }else{ 14 | resolve(stats) 15 | } 16 | }) 17 | }) 18 | } 19 | 20 | /** 21 | * 创建路径 22 | * @param {string} dir 路径 23 | */ 24 | function mkdir(dir){ 25 | return new Promise((resolve, reject) => { 26 | fs.mkdir(dir, err => { 27 | if(err){ 28 | resolve(false) 29 | }else{ 30 | resolve(true) 31 | } 32 | }) 33 | }) 34 | } 35 | 36 | /** 37 | * 路径是否存在,不存在则创建 38 | * @param {string} dir 路径 39 | */ 40 | async function dirExists(dir){ 41 | let isExists = await getStat(dir) 42 | // 如果该路径且不是文件,返回true 43 | if (isExists && isExists.isDirectory()) { 44 | return true 45 | } else if (isExists) { 46 | // 如果该路径存在但是文件,返回false 47 | return false 48 | } 49 | // 如果该路径不存在 拿到上级路径 50 | let tempDir = path.parse(dir).dir 51 | // 递归判断,如果上级目录也不存在,则会代码会在此处继续循环执行,直到目录存在 52 | let status = await dirExists(tempDir) 53 | let mkdirStatus 54 | if(status){ 55 | mkdirStatus = await mkdir(dir) 56 | } 57 | return mkdirStatus 58 | } 59 | 60 | module.exports = dirExists 61 | -------------------------------------------------------------------------------- /src/utils/snippets/cookies-snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "utils cookies set": { 3 | "scope": "javascript,typescript,vue,vue-html", 4 | "prefix": "utils cookies set", 5 | "body": [ 6 | "utils.cookies.set('${1:name}', ${2:'value'})" 7 | ], 8 | "description": "# D2Admin 内置工具包 | utils.cookies\n# 设置 cookie\n# 文档 https://d2.pub/zh/doc/d2-admin/util/#util-cookies-set" 9 | }, 10 | "utils cookies get": { 11 | "scope": "javascript,typescript,vue,vue-html", 12 | "prefix": "utils cookies get", 13 | "body": [ 14 | "utils.cookies.get('${1:name}')" 15 | ], 16 | "description": "# D2Admin 内置工具包 | utils.cookies\n# 获取 cookie\n# 文档 https://d2.pub/zh/doc/d2-admin/util/#util-cookies-get" 17 | }, 18 | "utils cookies getAll": { 19 | "scope": "javascript,typescript,vue,vue-html", 20 | "prefix": "utils cookies getAll", 21 | "body": [ 22 | "utils.cookies.getAll()" 23 | ], 24 | "description": "# D2Admin 内置工具包 | utils.cookies\n# 获取全部 cookie\n# 文档 https://d2.pub/zh/doc/d2-admin/util/#util-cookies-getall" 25 | }, 26 | "utils cookies remove": { 27 | "scope": "javascript,typescript,vue,vue-html", 28 | "prefix": "utils cookies remove", 29 | "body": [ 30 | "utils.cookies.remove('${1:name}')" 31 | ], 32 | "description": "# D2Admin 内置工具包 | utils.cookies\n# 删除 cookie\n# 文档 https://d2.pub/zh/doc/d2-admin/util/#util-cookies-remove" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/views/system/index/components/permission.vue: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /src/components/d2-table-actions/component.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'd2-table-actions', 3 | render () { 4 | const render = 5 | 6 | { 7 | this.actions.map( 8 | action => { 9 | const attrsDefault = { 10 | plain: true 11 | } 12 | const attrs = this._.omit(action, [ 13 | 'action' 14 | ]) 15 | const button = 16 | this.onAction(action) } 20 | /> 21 | return button 22 | } 23 | ) 24 | } 25 | 26 | return render 27 | }, 28 | props: { 29 | actions: { 30 | type: Array, 31 | default: () => [], 32 | required: false 33 | } 34 | }, 35 | methods: { 36 | onAction (action) { 37 | const callback = action.action || (() => {}) 38 | if (action.confirm) { 39 | this.$confirm(action.confirm, '提示', { 40 | confirmButtonText: '确定', 41 | cancelButtonText: '取消' 42 | }) 43 | .then(callback) 44 | .catch(() => {}) 45 | } else { 46 | callback() 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/mixins/crud-form-dialog.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data () { 3 | return { 4 | dialog: { 5 | visible: false, 6 | showClose: false, 7 | top: '0px', 8 | width: '400px', 9 | customClass: 'el-dialog__no-top-border', 10 | appendToBody: true 11 | } 12 | } 13 | }, 14 | computed: { 15 | // 表单容器的标题 16 | title () { 17 | return this.switchByMode('新建', '编辑') 18 | } 19 | }, 20 | methods: { 21 | /** 22 | * @description vNode 模态框 23 | * @param {VNode} content 内容 24 | */ 25 | vNodeDialog (content) { 26 | const node = 27 | 31 | { content } 32 | 33 | return node 34 | }, 35 | /** 36 | * @description 打开面板 37 | */ 38 | open () { 39 | this.dialog.visible = true 40 | this.$nextTick(() => this.clearValidate()) 41 | }, 42 | /** 43 | * @description 关闭面板 44 | */ 45 | cancle () { 46 | this.clearValidate() 47 | this.dialog.visible = false 48 | }, 49 | /** 50 | * @description 清空表单校验 51 | */ 52 | clearValidate () { 53 | this.$nextTick(() => { 54 | this.$refs.form && this.$refs.form.clearValidate() 55 | }) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/views/system/index/components/editor.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 31 | 32 | 47 | -------------------------------------------------------------------------------- /src/filters/snippets/time-snippets.js: -------------------------------------------------------------------------------- 1 | // dayjs must same as src/utils/modules/dayjs.js 2 | 3 | const dayjs = require('dayjs') 4 | const zh = require('dayjs/locale/zh-cn') 5 | const LocalizedFormat = require('dayjs/plugin/localizedFormat') 6 | const relativeTime = require('dayjs/plugin/relativeTime') 7 | const customizedLocale = { 8 | ...zh, 9 | meridiem: (hour, minute, isLowercase) => hour > 12 ? '下午' : '上午' 10 | } 11 | dayjs.locale(customizedLocale) 12 | dayjs.extend(LocalizedFormat) 13 | dayjs.extend(relativeTime) 14 | const now = dayjs() 15 | 16 | // https://github.com/iamkun/dayjs/blob/dev/docs/zh-cn/API-reference.md#显示 17 | 18 | exports.default = function () { 19 | const formatSettings = [ 20 | 'HH:mm', 21 | 'HH:mm:ss', 22 | 'YYYY/M/D', 23 | 'YYYY/MM/DD', 24 | 'YYYY年M月D日', 25 | 'YYYY年M月D日 HH:mm', 26 | 'YYYY年M月D日 dddd HH:mm', 27 | 'YYYY年M月D日 Ah点mm分', 28 | 'YYYY年M月D日 dddd Ah点mm分' 29 | ] 30 | let result = {} 31 | formatSettings.forEach(setting => { 32 | const body = `timeFormat('${setting}')` 33 | result[`vue filter timeFormat ${setting}`] = { 34 | scope: 'javascript,typescript,vue,vue-html', 35 | prefix: `filter timeFormat ${setting}`, 36 | body: [ body ], 37 | description: [ 38 | '# D2Admin 内置 Vue 过滤器', 39 | `渲染输出: ${now.format(setting)}` 40 | ].join('\n# ') 41 | } 42 | }) 43 | return result 44 | } 45 | -------------------------------------------------------------------------------- /src/components/d2-debounce/component.js: -------------------------------------------------------------------------------- 1 | // https://www.lodashjs.com/docs/latest#_debouncefunc-wait0-options 2 | 3 | export default { 4 | name: 'd2-debounce', 5 | functional: true, 6 | render (createElement, context) { 7 | // [options.leading=false] (boolean): 指定在延迟开始前调用 8 | // [options.maxWait] (number): 设置 func 允许被延迟的最大值 9 | // [options.trailing=true] (boolean): 指定在延迟结束后调用 10 | const options = ({ leading = false, maxWait, trailing = true } = {}) => ({ leading, maxWait, trailing })(context.props) 11 | const wait = context.props.wait 12 | const vnodeList = context.slots().default 13 | if (vnodeList === undefined) { 14 | console.warn(' 组件必须要有子元素') 15 | return null 16 | } 17 | const vnode = vnodeList[0] || null 18 | if (vnode.tag === 'button') { 19 | const defaultFun = vnode.data.on.click 20 | const debounceFun = this._.debounce(defaultFun, wait, options) 21 | vnode.data.on.click = debounceFun 22 | } else if (vnode.componentOptions && vnode.componentOptions.tag === 'el-button') { 23 | const defaultFun = vnode.componentOptions.listeners.click 24 | const debounceFun = this._.debounce(defaultFun, wait, options) 25 | vnode.componentOptions.listeners.click = debounceFun 26 | } else { 27 | console.warn(' 组件内只能出现下面组件的任意一个且唯一 el-button、button') 28 | return vnode 29 | } 30 | return vnode 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/d2-button/component.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'd2-button', 3 | render () { 4 | const button = 5 | 15 | { this.fa ? : undefined } 16 | { this.fa && (this.label || this.$slots.default) ? ' ' : undefined } 17 | { this.label ? this.label : undefined } 18 | { this.$slots.default } 19 | 20 | return button 21 | }, 22 | props: { 23 | fa: { 24 | type: String, 25 | default: '', 26 | required: false 27 | }, 28 | label: { 29 | type: String, 30 | default: '', 31 | required: false 32 | }, 33 | to: { 34 | type: [ String, Object ], 35 | default: '', 36 | required: false 37 | }, 38 | block: { 39 | type: Boolean, 40 | default: false, 41 | required: false 42 | }, 43 | thin: { 44 | type: Boolean, 45 | default: false, 46 | required: false 47 | } 48 | }, 49 | methods: { 50 | onClick (e) { 51 | if (this.to) { 52 | this.$router.push(this.to) 53 | } 54 | this.$emit('click', e) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/store/modules/d2admin/modules/search.js: -------------------------------------------------------------------------------- 1 | import setting from '@/setting.js' 2 | 3 | export default context => ({ 4 | namespaced: true, 5 | state: { 6 | // 搜索面板激活状态 7 | active: false, 8 | // 快捷键 9 | hotkey: { 10 | open: setting.hotkey.search.open, 11 | close: setting.hotkey.search.close 12 | }, 13 | // 所有可以搜索的页面 14 | pool: [] 15 | }, 16 | mutations: { 17 | /** 18 | * @description 切换激活状态 19 | * @param {Object} state state 20 | */ 21 | toggle (state) { 22 | state.active = !state.active 23 | }, 24 | /** 25 | * @description 设置激活模式 26 | * @param {Object} state state 27 | * @param {Boolean} active active 28 | */ 29 | set (state, active) { 30 | state.active = active 31 | }, 32 | /** 33 | * @description 初始化 34 | * @param {Object} state state 35 | * @param {Array} menu menu 36 | */ 37 | init (state, menu) { 38 | const pool = [] 39 | const push = function (menu, titlePrefix = []) { 40 | menu.forEach(m => { 41 | if (m.children) { 42 | push(m.children, [ ...titlePrefix, m.title ]) 43 | } else { 44 | pool.push({ 45 | ...m, 46 | fullTitle: [ ...titlePrefix, m.title ].join(' / ') 47 | }) 48 | } 49 | }) 50 | } 51 | push(menu) 52 | state.pool = pool 53 | } 54 | } 55 | }) 56 | -------------------------------------------------------------------------------- /src/components/d2-table/component.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'd2-table', 3 | props: { 4 | columns: { 5 | type: Array, 6 | default: () => [], 7 | required: false 8 | } 9 | }, 10 | render (createElement) { 11 | const propsDefault = { 12 | stripe: true, 13 | height: '100%', 14 | rowKey: 'id', 15 | treeProps: { 16 | children: 'children_list' 17 | } 18 | } 19 | return createElement('el-table', { 20 | ref: 'table', 21 | props: Object.assign(propsDefault, this.$attrs), 22 | on: this.$listeners, 23 | directives: [ 24 | { 25 | name: 'loading', 26 | value: this.$attrs.loading || false 27 | } 28 | ] 29 | }, this.columns.map(column => { 30 | const scopedSlots = column.render ? { 31 | scopedSlots: { 32 | default: scope => column.render(scope) 33 | } 34 | } : null 35 | return createElement('el-table-column', { 36 | props: column, 37 | ...scopedSlots || {} 38 | }) 39 | })) 40 | }, 41 | watch: { 42 | columns () { 43 | this.$nextTick(this.$refs.table.doLayout) 44 | } 45 | }, 46 | methods: { 47 | // https://element.eleme.cn/#/zh-CN/component/table#table-methods 48 | method (methodName, ...arg) { 49 | const fn = this.$refs.table[methodName] 50 | if (fn) { 51 | fn(...arg) 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/layout/header-aside/components/utils/menu.js: -------------------------------------------------------------------------------- 1 | // 创建 el-menu-item 2 | export function elMenuItem (createElement, menu) { 3 | return createElement('el-menu-item', { props: { index: menu.path } }, [ 4 | ...menu.icon ? [ 5 | createElement('i', { attrs: { class: `fa fa-${menu.icon}` } }) 6 | ] : [], 7 | // ...menu.icon === undefined & !menu.iconSvg ? [ 8 | // createElement('i', { attrs: { class: 'fa fa-file-o' } }) 9 | // ] : [], 10 | ...menu.iconSvg ? [ 11 | createElement('d2-icon-svg', { props: { name: menu.iconSvg } }) 12 | ] : [], 13 | createElement('span', { slot: 'title' }, menu.title || '未命名菜单') 14 | ]) 15 | } 16 | 17 | // 创建 el-submenu 18 | export function elSubmenu (createElement, menu) { 19 | return createElement('el-submenu', { props: { index: menu.path } }, [ 20 | ...menu.icon ? [ 21 | createElement('i', { slot: 'title', attrs: { class: `fa fa-${menu.icon}` } }) 22 | ] : [], 23 | // ...menu.icon === undefined & !menu.iconSvg ? [ 24 | // createElement('i', { slot: 'title', attrs: { class: 'fa fa-file-o' } }) 25 | // ] : [], 26 | ...menu.iconSvg ? [ 27 | createElement('d2-icon-svg', { slot: 'title', props: { name: menu.iconSvg } }) 28 | ] : [], 29 | createElement('span', { slot: 'title' }, menu.title || '未命名菜单'), 30 | ...menu.children.map((child, childIndex) => (child.children === undefined ? elMenuItem : elSubmenu).call(this, createElement, child)) 31 | ]) 32 | } 33 | -------------------------------------------------------------------------------- /src/layout/header-aside/components/header-log/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 55 | -------------------------------------------------------------------------------- /src/utils/snippets/db-snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "utils db dbSet": { 3 | "scope": "javascript,typescript,vue,vue-html", 4 | "prefix": "utils db dbSet", 5 | "body": [ 6 | "utils.db.dbSet({", 7 | " dbName: '${1:sys or database}',", 8 | " path: '${2:path like module.key}',", 9 | " value: ${3:'any data'},", 10 | " user: ${4:true}", 11 | "})" 12 | ], 13 | "description": "# D2Admin 内置工具包 | utils.db\n# 持久化数据\n# 文档 https://d2.pub/zh/doc/d2-admin/db" 14 | }, 15 | "utils db dbGet": { 16 | "scope": "javascript,typescript,vue,vue-html", 17 | "prefix": "utils db dbGet", 18 | "body": [ 19 | "utils.db.dbGet({", 20 | " dbName: '${1:sys or database}',", 21 | " path: '${2:path like module.key}',", 22 | " defaultValue: ${3:'any data'},", 23 | " user: ${4:true}", 24 | "})" 25 | ], 26 | "description": "# D2Admin 内置工具包 | utils.db\n# 获得持久化数据\n# 文档 https://d2.pub/zh/doc/d2-admin/db" 27 | }, 28 | "utils db database": { 29 | "scope": "javascript,typescript,vue,vue-html", 30 | "prefix": "utils db database", 31 | "body": [ 32 | "utils.db.database({", 33 | " dbName: '${1:sys or database}',", 34 | " path: '${2:path like module.key}',", 35 | " user: ${3:true},", 36 | " validator: ${4:() => true},", 37 | " defaultValue: ${5:'any data'},", 38 | "})" 39 | ], 40 | "description": "# D2Admin 内置工具包 | utils.db\n# 获得指定位置的存储实例\n# 文档 https://d2.pub/zh/doc/d2-admin/db" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/api/modules/dict.js: -------------------------------------------------------------------------------- 1 | export default ({ request }) => ({ 2 | /** 3 | * @description 字典列表 4 | * @description http://yapi.xiya.vip/project/11/interface/api/75 5 | */ 6 | DICT_ALL (query = {}) { 7 | return request({ 8 | url: '/api/dict/index', 9 | method: 'post', 10 | data: query 11 | }) 12 | }, 13 | /** 14 | * @description 字典创建 15 | * @description http://yapi.xiya.vip/project/11/interface/api/80 16 | */ 17 | DICT_CREATE (data) { 18 | return request({ 19 | url: '/api/dict/create', 20 | method: 'put', 21 | data 22 | }) 23 | }, 24 | /** 25 | * @description 字典详情 26 | * @description http://yapi.xiya.vip/project/11/interface/api/95 27 | */ 28 | DICT_DETAIL (id) { 29 | return request({ 30 | url: '/api/dict/update', 31 | method: 'post', 32 | data: { 33 | id 34 | } 35 | }) 36 | }, 37 | /** 38 | * @description 字典编辑 39 | * @description http://yapi.xiya.vip/project/11/interface/api/95 40 | */ 41 | DICT_UPDATE (data) { 42 | return request({ 43 | url: '/api/dict/update', 44 | method: 'put', 45 | data 46 | }) 47 | }, 48 | /** 49 | * @description 字典删除 50 | * @description http://yapi.xiya.vip/project/11/interface/api/90 51 | */ 52 | DICT_DELETE (id) { 53 | return request({ 54 | url: '/api/dict/delete', 55 | method: 'delete', 56 | data: { 57 | id 58 | } 59 | }) 60 | } 61 | }) 62 | -------------------------------------------------------------------------------- /src/api/modules/post.js: -------------------------------------------------------------------------------- 1 | export default ({ request }) => ({ 2 | /** 3 | * @description 岗位列表 4 | * @description http://yapi.xiya.vip/project/11/interface/api/75 5 | */ 6 | POST_ALL (query = {}) { 7 | return request({ 8 | url: '/api/post/index', 9 | method: 'post', 10 | data: query 11 | }) 12 | }, 13 | /** 14 | * @description 岗位创建 15 | * @description http://yapi.xiya.vip/project/11/interface/api/80 16 | */ 17 | POST_CREATE (data) { 18 | return request({ 19 | url: '/api/post/create', 20 | method: 'put', 21 | data 22 | }) 23 | }, 24 | /** 25 | * @description 岗位详情 26 | * @description http://yapi.xiya.vip/project/11/interface/api/95 27 | */ 28 | POST_DETAIL (id) { 29 | return request({ 30 | url: '/api/post/update', 31 | method: 'post', 32 | data: { 33 | id 34 | } 35 | }) 36 | }, 37 | /** 38 | * @description 岗位编辑 39 | * @description http://yapi.xiya.vip/project/11/interface/api/95 40 | */ 41 | POST_UPDATE (data) { 42 | return request({ 43 | url: '/api/post/update', 44 | method: 'put', 45 | data 46 | }) 47 | }, 48 | /** 49 | * @description 岗位删除 50 | * @description http://yapi.xiya.vip/project/11/interface/api/90 51 | */ 52 | POST_DELETE (id) { 53 | return request({ 54 | url: '/api/post/delete', 55 | method: 'delete', 56 | data: { 57 | id 58 | } 59 | }) 60 | } 61 | }) 62 | -------------------------------------------------------------------------------- /src/layout/header-aside/components/contextmenu/components/contentmenuList/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 43 | 44 | 62 | -------------------------------------------------------------------------------- /src/api/modules/role.js: -------------------------------------------------------------------------------- 1 | export default ({ request }) => ({ 2 | /** 3 | * @description 角色列表 4 | * @description http://yapi.xiya.vip/project/11/interface/api/215 5 | */ 6 | ROLE_ALL (query = {}) { 7 | return request({ 8 | url: '/api/role/index', 9 | method: 'post', 10 | data: query 11 | }) 12 | }, 13 | /** 14 | * @description 角色创建 15 | * @description http://yapi.xiya.vip/project/11/interface/api/220 16 | */ 17 | ROLE_CREATE (data) { 18 | return request({ 19 | url: '/api/role/create', 20 | method: 'put', 21 | data 22 | }) 23 | }, 24 | /** 25 | * @description 角色详情 26 | * @description http://yapi.xiya.vip/project/11/interface/api/230 27 | */ 28 | ROLE_DETAIL (id) { 29 | return request({ 30 | url: '/api/role/update', 31 | method: 'post', 32 | data: { 33 | id 34 | } 35 | }) 36 | }, 37 | /** 38 | * @description 角色编辑 39 | * @description http://yapi.xiya.vip/project/11/interface/api/230 40 | */ 41 | ROLE_UPDATE (data) { 42 | return request({ 43 | url: '/api/role/update', 44 | method: 'put', 45 | data 46 | }) 47 | }, 48 | /** 49 | * @description 角色删除 50 | * @description http://yapi.xiya.vip/project/11/interface/api/225 51 | */ 52 | ROLE_DELETE (id) { 53 | return request({ 54 | url: '/api/role/delete', 55 | method: 'delete', 56 | data: { 57 | id 58 | } 59 | }) 60 | } 61 | }) 62 | -------------------------------------------------------------------------------- /src/api/modules/dept.js: -------------------------------------------------------------------------------- 1 | export default ({ request }) => ({ 2 | /** 3 | * @description 部门列表 4 | * @description 获取所有部门的层级关系 5 | * @description http://yapi.xiya.vip/project/11/interface/api/195 6 | */ 7 | DEPT_ALL (query = {}) { 8 | return request({ 9 | url: '/api/dept/findall', 10 | method: 'post', 11 | data: query 12 | }) 13 | }, 14 | /** 15 | * @description 部门创建 16 | * @description http://yapi.xiya.vip/project/11/interface/api/200 17 | */ 18 | DEPT_CREATE (data) { 19 | return request({ 20 | url: '/api/dept/create', 21 | method: 'put', 22 | data 23 | }) 24 | }, 25 | /** 26 | * @description 部门详情 27 | * @description http://yapi.xiya.vip/project/11/interface/api/210 28 | */ 29 | DEPT_DETAIL (id) { 30 | return request({ 31 | url: '/api/dept/update', 32 | method: 'post', 33 | data: { 34 | id 35 | } 36 | }) 37 | }, 38 | /** 39 | * @description 部门编辑 40 | * @description http://yapi.xiya.vip/project/11/interface/api/210 41 | */ 42 | DEPT_UPDATE (data) { 43 | return request({ 44 | url: '/api/dept/update', 45 | method: 'put', 46 | data 47 | }) 48 | }, 49 | /** 50 | * @description 部门删除 51 | * @description http://yapi.xiya.vip/project/11/interface/api/205 52 | */ 53 | DEPT_DELETE (id) { 54 | return request({ 55 | url: '/api/dept/delete', 56 | method: 'delete', 57 | data: { 58 | id 59 | } 60 | }) 61 | } 62 | }) 63 | -------------------------------------------------------------------------------- /src/api/modules/dict-data.js: -------------------------------------------------------------------------------- 1 | export default ({ request }) => ({ 2 | /** 3 | * @description 字典数据列表 4 | * @description http://yapi.xiya.vip/project/11/interface/api/75 5 | */ 6 | DICTDATA_ALL (query = {}) { 7 | return request({ 8 | url: '/api/dictData/index', 9 | method: 'post', 10 | data: query 11 | }) 12 | }, 13 | /** 14 | * @description 字典数据创建 15 | * @description http://yapi.xiya.vip/project/11/interface/api/80 16 | */ 17 | DICTDATA_CREATE (data) { 18 | return request({ 19 | url: '/api/dictData/create', 20 | method: 'put', 21 | data 22 | }) 23 | }, 24 | /** 25 | * @description 字典数据详情 26 | * @description http://yapi.xiya.vip/project/11/interface/api/95 27 | */ 28 | DICTDATA_DETAIL (id) { 29 | return request({ 30 | url: '/api/dictData/update', 31 | method: 'post', 32 | data: { 33 | id 34 | } 35 | }) 36 | }, 37 | /** 38 | * @description 字典数据编辑 39 | * @description http://yapi.xiya.vip/project/11/interface/api/95 40 | */ 41 | DICTDATA_UPDATE (data) { 42 | return request({ 43 | url: '/api/dictData/update', 44 | method: 'put', 45 | data 46 | }) 47 | }, 48 | /** 49 | * @description 字典数据删除 50 | * @description http://yapi.xiya.vip/project/11/interface/api/90 51 | */ 52 | DICTDATA_DELETE (id) { 53 | return request({ 54 | url: '/api/dictData/delete', 55 | method: 'delete', 56 | data: { 57 | id 58 | } 59 | }) 60 | } 61 | }) 62 | -------------------------------------------------------------------------------- /src/setting.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // 快捷键 3 | // 支持快捷键 例如 ctrl+shift+s 4 | hotkey: { 5 | search: { 6 | open: 's', 7 | close: 'esc' 8 | } 9 | }, 10 | // 侧边栏默认折叠状态 11 | menu: { 12 | asideCollapse: false 13 | }, 14 | // 在读取持久化数据失败时默认页面 15 | page: { 16 | opened: [ 17 | { 18 | name: 'index', 19 | fullPath: '/index', 20 | meta: { 21 | title: '首页', 22 | auth: false 23 | } 24 | } 25 | ] 26 | }, 27 | // 菜单搜索 28 | search: { 29 | enable: true 30 | }, 31 | // 注册的主题 32 | theme: { 33 | list: [ 34 | { 35 | title: 'd2admin 经典', 36 | name: 'd2', 37 | preview: 'image/theme/d2/preview@2x.png' 38 | }, 39 | { 40 | title: '紫罗兰', 41 | name: 'violet', 42 | preview: 'image/theme/violet/preview@2x.png' 43 | }, 44 | { 45 | title: '简约线条', 46 | name: 'line', 47 | backgroundImage: 'image/theme/line/bg.jpg', 48 | preview: 'image/theme/line/preview@2x.png' 49 | }, 50 | { 51 | title: '流星', 52 | name: 'star', 53 | backgroundImage: 'image/theme/star/bg.jpg', 54 | preview: 'image/theme/star/preview@2x.png' 55 | }, 56 | { 57 | title: 'Tomorrow Night Blue (vsCode)', 58 | name: 'tomorrow-night-blue', 59 | preview: 'image/theme/tomorrow-night-blue/preview@2x.png' 60 | } 61 | ] 62 | }, 63 | // 是否默认开启页面切换动画 64 | transition: { 65 | active: true 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/views/management/role/form2.js: -------------------------------------------------------------------------------- 1 | import form from '@/mixins/crud-form' 2 | 3 | export default { 4 | mixins: [ form ], 5 | data () { 6 | return { 7 | api: { 8 | detail: 'ROLE_DETAIL', 9 | create: 'ROLE_CREATE', 10 | update: 'ROLE_UPDATE' 11 | } 12 | } 13 | }, 14 | computed: { 15 | setting () { 16 | console.log('xxxxxxxxxxxxxx') 17 | console.log(this.form.model.data_scope) 18 | return [ 19 | { 20 | prop: 'data_scope', 21 | default: this.$env.VUE_APP_DICT_EMPTY_NUMBER, 22 | label: '数据范围', 23 | rule: { required: true, message: '必填', trigger: 'change' }, 24 | render: () => 25 | }, 26 | ...this.form.model.data_scope === this.$env.VUE_APP_DICT_DATA_SCOPE_CUSTOM ? [ 27 | { 28 | prop: 'role_dept', 29 | default: '', 30 | label: '部门权限', 31 | render: () => 32 | } 33 | ] : [] 34 | ] 35 | } 36 | }, 37 | methods: { 38 | onDataScopeChange (dataScope) { 39 | this.modelReload({ 40 | pick: [ 41 | 'role_name', 42 | 'role_key', 43 | 'data_scope' 44 | ], 45 | // 全部数据权限时 清空部门权限 46 | data: dataScope === this.$env.VUE_APP_DICT_DATA_SCOPE_ALL ? { role_dept: '' } : {} 47 | }) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/components/d2-container/components/mixins/bs.js: -------------------------------------------------------------------------------- 1 | import BScroll from 'better-scroll' 2 | export default { 3 | props: { 4 | // 滚动优化的选项 5 | betterScrollOptions: { 6 | type: Object, 7 | required: false, 8 | default: () => ({}) 9 | }, 10 | // 更大的空间 11 | spacious: { 12 | type: Boolean, 13 | required: false, 14 | default: false 15 | } 16 | }, 17 | data () { 18 | return { 19 | BS: null 20 | } 21 | }, 22 | mounted () { 23 | this.scrollInit() 24 | }, 25 | beforeDestroy () { 26 | this.scrollDestroy() 27 | }, 28 | methods: { 29 | scrollInit () { 30 | // 初始化 bs 31 | this.BS = new BScroll(this.$refs.wrapper, Object.assign({ 32 | mouseWheel: true, 33 | click: true, 34 | scrollbar: { 35 | fade: true, 36 | interactive: false 37 | } 38 | }, this.betterScrollOptions)) 39 | // 滚动时发出事件 并且统一返回的数据格式 40 | this.BS.on('scroll', ({ x, y }) => this.$emit('scroll', { 41 | x: -x, 42 | y: -y 43 | })) 44 | }, 45 | scrollDestroy () { 46 | // https://github.com/d2-projects/d2-admin/issues/75 47 | try { 48 | this.BS.destroy() 49 | } catch (e) { 50 | delete this.BS 51 | this.BS = null 52 | } 53 | }, 54 | // 外部调用的方法 返回顶部 55 | scrollToTop () { 56 | if (this.BS) this.BS.scrollTo(0, 0, 300) 57 | }, 58 | // 手动发出滚动事件 59 | scroll () { 60 | if (this.BS) { 61 | this.$emit('scroll', { 62 | x: -this.BS.x, 63 | y: -this.BS.y 64 | }) 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/views/management/post/form.js: -------------------------------------------------------------------------------- 1 | import form from '@/mixins/crud-form' 2 | 3 | export default { 4 | mixins: [ form ], 5 | data () { 6 | return { 7 | api: { 8 | detail: 'POST_DETAIL', 9 | create: 'POST_CREATE', 10 | update: 'POST_UPDATE' 11 | } 12 | } 13 | }, 14 | computed: { 15 | setting () { 16 | return [ 17 | { 18 | prop: 'post_name', 19 | default: '', 20 | label: '岗位名称', 21 | rule: { required: true, message: '必填', trigger: 'change' }, 22 | render: () => 23 | }, 24 | { 25 | prop: 'post_code', 26 | default: '', 27 | label: '岗位编码', 28 | rule: { required: true, message: '必填', trigger: 'change' }, 29 | render: () => 30 | }, 31 | { 32 | prop: 'post_sort', 33 | default: this.$env.VUE_APP_FORM_SORT_MIN, 34 | label: '显示顺序', 35 | render: () => 36 | }, 37 | { 38 | prop: 'status', 39 | default: this.$env.VUE_APP_DICT_STATUS_ACTIVE, 40 | label: '状态', 41 | render: () => 42 | }, 43 | { 44 | prop: 'remark', 45 | default: '', 46 | label: '备注', 47 | render: () => 48 | } 49 | ] 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/layout/header-aside/components/contextmenu/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 60 | 61 | 72 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # 所有环境默认 2 | 3 | # 页面 title 前缀 4 | VUE_APP_TITLE=D2Admin 5 | 6 | # 默认账号密码 7 | VUE_APP_USER=admin 8 | VUE_APP_PASSWORD=admin@xiya.vip 9 | 10 | # 网络请求地址组 11 | # VUE_APP_API_[index]_[name]_[title]=url 12 | # 将会自动生成界面中的选项 13 | # 按 index 字段排序 14 | # index 最小的地址被默认激活 15 | VUE_APP_API_0_TEST=http://127.0.0.1:5000 16 | #VUE_APP_API_0_TEST=http://192.168.68.128:8888 17 | #VUE_APP_API_1_DEV=http://192.168.68.128:8088 18 | 19 | # 也可单独设置一个网络请求地址 优先级大于地址组 20 | # VUE_APP_API=http://192.168.68.128:8088 21 | 22 | # 仓库地址 23 | VUE_APP_REPO=https://github.com/d2-projects/d2-admin-xiya-go-cms 24 | 25 | # 国际化配置 26 | VUE_APP_I18N_LOCALE=zh-chs 27 | VUE_APP_I18N_FALLBACK_LOCALE=en 28 | 29 | # 腾讯防水墙 APPID 30 | VUE_APP_TENCENT_CAPTCHA_APP_ID=2076088864|string 31 | 32 | # 字典约定 33 | # 为了在字典发生变化时更容易修改涉及到的逻辑 34 | # 请务必确保这里的设置和字典设置一致 35 | # 请只在这里设置代码中需要的字典 切勿全部设置 36 | # 通常来讲只需要设置字典在表单中默认值的项目即可 37 | # VUE_APP_[字典名]_[字典类型]_[含义]=[值]|[值类型] 38 | 39 | # is 40 | VUE_APP_DICT_IS_TRUE=1|number 41 | VUE_APP_DICT_IS_FALSE=2|number 42 | 43 | # status 44 | VUE_APP_DICT_STATUS_ACTIVE=1|number 45 | VUE_APP_DICT_STATUS_DISABLED=2|number 46 | 47 | # visible 48 | VUE_APP_DICT_VISIBLE_TRUE=1|number 49 | VUE_APP_DICT_VISIBLE_FALSE=2|number 50 | 51 | # menu_type 52 | VUE_APP_DICT_MENU_TYPE_MENU=1|number 53 | VUE_APP_DICT_MENU_TYPE_BUTTON=2|number 54 | 55 | # data_scope 56 | VUE_APP_DICT_DATA_SCOPE_ALL=1|number 57 | VUE_APP_DICT_DATA_SCOPE_CUSTOM=2|number 58 | 59 | # dict_value_type 60 | VUE_APP_DICT_DICT_VALUE_TYPE_NUMBER=1|number 61 | 62 | # 数字类型的字典的空值 63 | VUE_APP_DICT_EMPTY_NUMBER=0|number 64 | # 字符串类型的字典的空值 empty|string 的写法最后会被转换为空字符串 65 | VUE_APP_DICT_EMPTY_STRING=empty|string 66 | 67 | # 表单排序类字段的最小值 68 | VUE_APP_FORM_SORT_MIN=1|number 69 | -------------------------------------------------------------------------------- /src/views/management/config/form.js: -------------------------------------------------------------------------------- 1 | import form from '@/mixins/crud-form' 2 | 3 | export default { 4 | mixins: [ form ], 5 | data () { 6 | return { 7 | api: { 8 | detail: 'CONFIG_DETAIL', 9 | create: 'CONFIG_CREATE', 10 | update: 'CONFIG_UPDATE' 11 | } 12 | } 13 | }, 14 | computed: { 15 | setting () { 16 | return [ 17 | { 18 | prop: 'config_name', 19 | default: '', 20 | label: '参数名称', 21 | rule: { required: true, message: '必填', trigger: 'change' }, 22 | render: () => 23 | }, 24 | { 25 | prop: 'config_key', 26 | default: '', 27 | label: '参数键名', 28 | rule: { required: true, message: '必填', trigger: 'change' }, 29 | render: () => 30 | }, 31 | { 32 | prop: 'config_value', 33 | default: '', 34 | label: '参数键值', 35 | rule: { required: true, message: '必填', trigger: 'change' }, 36 | render: () => 37 | }, 38 | { 39 | prop: 'config_type', 40 | default: this.$env.VUE_APP_DICT_IS_TRUE, 41 | label: '系统内置', 42 | render: () => 43 | }, 44 | { 45 | prop: 'remark', 46 | default: '', 47 | label: '备注', 48 | render: () => 49 | } 50 | ] 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/views/management/dict/form.js: -------------------------------------------------------------------------------- 1 | import form from '@/mixins/crud-form' 2 | 3 | export default { 4 | mixins: [ form ], 5 | data () { 6 | return { 7 | api: { 8 | detail: 'DICT_DETAIL', 9 | create: 'DICT_CREATE', 10 | update: 'DICT_UPDATE' 11 | } 12 | } 13 | }, 14 | computed: { 15 | setting () { 16 | return [ 17 | { 18 | prop: 'dict_name', 19 | default: '', 20 | label: '名称', 21 | rule: { required: true, message: '必填', trigger: 'change' }, 22 | render: () => 23 | }, 24 | { 25 | prop: 'dict_type', 26 | default: '', 27 | label: '标识', 28 | rule: { required: true, message: '必填', trigger: 'change' }, 29 | render: () => 30 | }, 31 | { 32 | prop: 'dict_value_type', 33 | default: this.$env.VUE_APP_DICT_DICT_VALUE_TYPE_NUMBER, 34 | label: '数据类型', 35 | rule: { required: true, message: '必填', trigger: 'change' }, 36 | render: () => 37 | }, 38 | { 39 | prop: 'status', 40 | default: this.$env.VUE_APP_DICT_STATUS_ACTIVE, 41 | label: '状态', 42 | render: () => 43 | }, 44 | { 45 | prop: 'remark', 46 | default: '', 47 | label: '备注', 48 | render: () => 49 | } 50 | ] 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/layout/header-aside/mixins/search.js: -------------------------------------------------------------------------------- 1 | import { mapState, mapMutations } from 'vuex' 2 | 3 | import hotkeys from 'hotkeys-js' 4 | 5 | export default { 6 | components: { 7 | 'd2-panel-search': () => import('../components/panel-search') 8 | }, 9 | mounted () { 10 | // 绑定搜索功能快捷键 [ 打开 ] 11 | hotkeys(this.searchHotkey.open, event => { 12 | event.preventDefault() 13 | this.searchPanelOpen() 14 | }) 15 | // 绑定搜索功能快捷键 [ 关闭 ] 16 | hotkeys(this.searchHotkey.close, event => { 17 | event.preventDefault() 18 | this.searchPanelClose() 19 | }) 20 | }, 21 | beforeDestroy () { 22 | hotkeys.unbind(this.searchHotkey.open) 23 | hotkeys.unbind(this.searchHotkey.close) 24 | }, 25 | computed: { 26 | ...mapState('d2admin', { 27 | searchActive: state => state.search.active, 28 | searchHotkey: state => state.search.hotkey 29 | }) 30 | }, 31 | methods: { 32 | ...mapMutations({ 33 | searchToggle: 'd2admin/search/toggle', 34 | searchSet: 'd2admin/search/set' 35 | }), 36 | /** 37 | * 接收点击搜索按钮 38 | */ 39 | handleSearchClick () { 40 | this.searchToggle() 41 | if (this.searchActive) { 42 | setTimeout(() => { 43 | if (this.$refs.panelSearch) { 44 | this.$refs.panelSearch.focus() 45 | } 46 | }, 500) 47 | } 48 | }, 49 | searchPanelOpen () { 50 | if (!this.searchActive) { 51 | this.searchSet(true) 52 | setTimeout(() => { 53 | if (this.$refs.panelSearch) { 54 | this.$refs.panelSearch.focus() 55 | } 56 | }, 500) 57 | } 58 | }, 59 | // 关闭搜索面板 60 | searchPanelClose () { 61 | if (this.searchActive) { 62 | this.searchSet(false) 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/api/modules/config.js: -------------------------------------------------------------------------------- 1 | export default ({ request }) => ({ 2 | /** 3 | * @description 参数列表 4 | * @description http://yapi.xiya.vip/project/11/interface/api/75 5 | */ 6 | CONFIG_ALL (query = {}) { 7 | return request({ 8 | url: '/api/configs/index', 9 | method: 'post', 10 | data: query 11 | }) 12 | }, 13 | /** 14 | * @description 参数列表 获取前端或者后端使用的参数列表 无分页 15 | * @description http://yapi.xiya.vip/project/11/interface/api/620 16 | */ 17 | CONFIG_FIND (type = 0) { 18 | return request({ 19 | url: '/api/configs/find_all', 20 | method: 'post', 21 | data: { 22 | type 23 | } 24 | }) 25 | }, 26 | /** 27 | * @description 参数创建 28 | * @description http://yapi.xiya.vip/project/11/interface/api/80 29 | */ 30 | CONFIG_CREATE (data) { 31 | return request({ 32 | url: '/api/configs/create', 33 | method: 'put', 34 | data 35 | }) 36 | }, 37 | /** 38 | * @description 参数详情 39 | * @description http://yapi.xiya.vip/project/11/interface/api/95 40 | */ 41 | CONFIG_DETAIL (id) { 42 | return request({ 43 | url: '/api/configs/update', 44 | method: 'post', 45 | data: { 46 | id 47 | } 48 | }) 49 | }, 50 | /** 51 | * @description 参数编辑 52 | * @description http://yapi.xiya.vip/project/11/interface/api/95 53 | */ 54 | CONFIG_UPDATE (data) { 55 | return request({ 56 | url: '/api/configs/update', 57 | method: 'put', 58 | data 59 | }) 60 | }, 61 | /** 62 | * @description 参数删除 63 | * @description http://yapi.xiya.vip/project/11/interface/api/90 64 | */ 65 | CONFIG_DELETE (id) { 66 | return request({ 67 | url: '/api/configs/delete', 68 | method: 'delete', 69 | data: { 70 | id 71 | } 72 | }) 73 | } 74 | }) 75 | -------------------------------------------------------------------------------- /src/layout/header-aside/components/header-theme/components/d2-theme-list/index.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 68 | 69 | 78 | -------------------------------------------------------------------------------- /src/components/d2-container/components/mixins/normal.js: -------------------------------------------------------------------------------- 1 | // 提供滚动方面的功能 2 | // 非滚动优化模式通用 3 | 4 | export default { 5 | props: { 6 | // 滚动事件节流间隔 7 | scrollDelay: { 8 | type: Number, 9 | required: false, 10 | default: 10 11 | }, 12 | // 更大的空间 13 | spacious: { 14 | type: Boolean, 15 | required: false, 16 | default: false 17 | } 18 | }, 19 | data () { 20 | return { 21 | handleScroll: null 22 | } 23 | }, 24 | watch: { 25 | scrollDelay (val) { 26 | // 移除旧的监听 27 | this.removeScrollListener() 28 | // 生成新的 handle 方法 29 | this.handleScroll = this.handleMaker(val) 30 | // 添加新的监听 31 | this.addScrollListener() 32 | } 33 | }, 34 | methods: { 35 | // 生成滚动事件的 handler 36 | handleMaker (wait) { 37 | return this._.throttle(e => { 38 | this.$emit('scroll', { 39 | x: e.target.scrollLeft, 40 | y: e.target.scrollTop 41 | }) 42 | }, wait) 43 | }, 44 | // 增加滚动事件监听 45 | addScrollListener () { 46 | if (typeof this.handleScroll !== 'function') { 47 | // mounted 生命周期内调用这个方法的时候会进入这里的判断 48 | this.handleScroll = this.handleMaker(this.scrollDelay) 49 | } 50 | // 添加监听 51 | this.$refs.body.addEventListener('scroll', this.handleScroll) 52 | }, 53 | // 移除滚动事件监听 54 | removeScrollListener () { 55 | this.$refs.body.removeEventListener('scroll', this.handleScroll) 56 | }, 57 | // 外部调用的方法 返回顶部 58 | scrollToTop () { 59 | const smoothscroll = () => { 60 | const body = this.$refs.body 61 | const currentScroll = body.scrollTop 62 | if (currentScroll > 0) { 63 | window.requestAnimationFrame(smoothscroll) 64 | body.scrollTo(0, currentScroll - (currentScroll / 5)) 65 | } 66 | } 67 | smoothscroll() 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/views/management/role/form.js: -------------------------------------------------------------------------------- 1 | import form from '@/mixins/crud-form' 2 | 3 | export default { 4 | mixins: [ form ], 5 | data () { 6 | return { 7 | api: { 8 | detail: 'ROLE_DETAIL', 9 | create: 'ROLE_CREATE', 10 | update: 'ROLE_UPDATE' 11 | } 12 | } 13 | }, 14 | computed: { 15 | setting () { 16 | return [ 17 | { 18 | prop: 'role_name', 19 | default: '', 20 | label: '角色名称', 21 | rule: { required: true, message: '必填', trigger: 'change' }, 22 | render: () => 23 | }, 24 | { 25 | prop: 'role_key', 26 | default: '', 27 | label: '权限字符', 28 | rule: { required: true, message: '必填', trigger: 'change' }, 29 | render: () => 30 | }, 31 | { 32 | prop: 'role_sort', 33 | default: this.$env.VUE_APP_FORM_SORT_MIN, 34 | label: '显示顺序', 35 | render: () => 36 | }, 37 | { 38 | prop: 'role_menu', 39 | default: '', 40 | label: '菜单权限', 41 | render: () => 42 | }, 43 | { 44 | prop: 'status', 45 | default: this.$env.VUE_APP_DICT_STATUS_ACTIVE, 46 | label: '状态', 47 | render: () => 48 | }, 49 | { 50 | prop: 'remark', 51 | default: '', 52 | label: '备注', 53 | render: () => 54 | } 55 | ] 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/utils/snippets/log-snippets.json: -------------------------------------------------------------------------------- 1 | { 2 | "utils log capsule": { 3 | "scope": "javascript,typescript,vue,vue-html", 4 | "prefix": "utils log capsule", 5 | "body": [ 6 | "utils.log.capsule('${1:title}', '${2:info}', '${3:default|primary|success|warning|danger}')" 7 | ], 8 | "description": "# D2Admin 内置工具包 | utils.log\n# 打印一个 [ title | text ] 样式的信息" 9 | }, 10 | "utils log colorful": { 11 | "scope": "javascript,typescript,vue,vue-html", 12 | "prefix": "utils log colorful", 13 | "body": [ 14 | "utils.log.colorful([", 15 | " { text: '${1:text}', type: '${2:default|primary|success|warning|danger}' }", 16 | "])" 17 | ], 18 | "description": "# D2Admin 内置工具包 | utils.log\n# 打印彩色文字" 19 | }, 20 | "utils log print": { 21 | "scope": "javascript,typescript,vue,vue-html", 22 | "prefix": "utils log print", 23 | "body": [ 24 | "utils.log.print('${1:text}')" 25 | ], 26 | "description": "# D2Admin 内置工具包 | utils.log\n# 打印 default 样式的文字" 27 | }, 28 | "utils log primary": { 29 | "scope": "javascript,typescript,vue,vue-html", 30 | "prefix": "utils log primary", 31 | "body": [ 32 | "utils.log.primary('${1:text}')" 33 | ], 34 | "description": "# D2Admin 内置工具包 | utils.log\n# 打印 primary 样式的文字" 35 | }, 36 | "utils log success": { 37 | "scope": "javascript,typescript,vue,vue-html", 38 | "prefix": "utils log success", 39 | "body": [ 40 | "utils.log.success('${1:text}')" 41 | ], 42 | "description": "# D2Admin 内置工具包 | utils.log\n# 打印 success 样式的文字" 43 | }, 44 | "utils log warning": { 45 | "scope": "javascript,typescript,vue,vue-html", 46 | "prefix": "utils log warning", 47 | "body": [ 48 | "utils.log.warning('${1:text}')" 49 | ], 50 | "description": "# D2Admin 内置工具包 | utils.log\n# 打印 warning 样式的文字" 51 | }, 52 | "utils log danger": { 53 | "scope": "javascript,typescript,vue,vue-html", 54 | "prefix": "utils log danger", 55 | "body": [ 56 | "utils.log.danger('${1:text}')" 57 | ], 58 | "description": "# D2Admin 内置工具包 | utils.log\n# 打印 danger 样式的文字" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/utils/modules/log.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 返回这个样式的颜色值 3 | * @param {String} type 样式名称 [ primary | success | warning | danger | text ] 4 | */ 5 | function typeColor (type = 'default') { 6 | let color = '' 7 | switch (type) { 8 | case 'default': color = '#35495E'; break 9 | case 'primary': color = '#3488ff'; break 10 | case 'success': color = '#43B883'; break 11 | case 'warning': color = '#e6a23c'; break 12 | case 'danger': color = '#f56c6c'; break 13 | default:; break 14 | } 15 | return color 16 | } 17 | 18 | /** 19 | * @description 打印一个 [ title | text ] 样式的信息 20 | * @param {String} title title text 21 | * @param {String} info info text 22 | * @param {String} type style 23 | */ 24 | export function capsule (title, info, type = 'primary') { 25 | console.log( 26 | `%c ${title} %c ${info} %c`, 27 | 'background:#35495E; padding: 1px; border-radius: 3px 0 0 3px; color: #fff;', 28 | `background:${typeColor(type)}; padding: 1px; border-radius: 0 3px 3px 0; color: #fff;`, 29 | 'background:transparent' 30 | ) 31 | } 32 | 33 | /** 34 | * @description 打印彩色文字 35 | */ 36 | export function colorful (textArr) { 37 | console.log( 38 | `%c${textArr.map(t => t.text || '').join('%c')}`, 39 | ...textArr.map(t => `color: ${typeColor(t.type)};`) 40 | ) 41 | } 42 | 43 | /** 44 | * @description 打印 default 样式的文字 45 | */ 46 | export function print (text) { 47 | colorful([{ text }]) 48 | } 49 | 50 | /** 51 | * @description 打印 primary 样式的文字 52 | */ 53 | export function primary (text) { 54 | colorful([{ text, type: 'primary' }]) 55 | } 56 | 57 | /** 58 | * @description 打印 success 样式的文字 59 | */ 60 | export function success (text) { 61 | colorful([{ text, type: 'success' }]) 62 | } 63 | 64 | /** 65 | * @description 打印 warning 样式的文字 66 | */ 67 | export function warning (text) { 68 | colorful([{ text, type: 'warning' }]) 69 | } 70 | 71 | /** 72 | * @description 打印 danger 样式的文字 73 | */ 74 | export function danger (text) { 75 | colorful([{ text, type: 'danger' }]) 76 | } 77 | -------------------------------------------------------------------------------- /src/components/d2-container/components/d2-source.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 48 | 49 | 81 | -------------------------------------------------------------------------------- /src/mixins/crud-status.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data () { 3 | return { 4 | status: { 5 | isLoadingData: false, 6 | isLoadingDict: false, 7 | isSubmitting: false 8 | } 9 | } 10 | }, 11 | computed: { 12 | // 表单 loading 状态 13 | // 正在加载原始数据 || 正在加载字典 14 | isFormLoading () { 15 | return this.status.isLoadingData || this.status.isLoadingDict 16 | }, 17 | // 表单 禁用 状态 18 | // 正在加载原始数据 || 正在加载字典 || 正在提交 19 | isFormDisabled () { 20 | return this.status.isLoadingData || this.status.isLoadingDict || this.status.isSubmitting 21 | }, 22 | // 提交按钮 禁用 状态 23 | // 正在加载原始数据 || 正在加载字典 || 表单没有发生修改 24 | isSubmitButtonDisabled () { 25 | return this.status.isLoadingData || this.status.isLoadingDict || !this.isFormChanged 26 | }, 27 | // 表单是否发生变化 28 | isFormChanged () { 29 | return true 30 | }, 31 | // 提交按钮 loading 状态 32 | // 正在提交 33 | isSubmitButtonLoading () { 34 | return this.status.isSubmitting 35 | } 36 | }, 37 | methods: { 38 | /** 39 | * @description 请求表单数据 40 | * @param {Function} fn 请求函数 需要返回 Promise 41 | */ 42 | async doLoadData (fn = () => {}) { 43 | this.status.isLoadingData = true 44 | try { 45 | const data = await fn() 46 | this.status.isLoadingData = false 47 | return Promise.resolve(data) 48 | } catch (error) { 49 | console.log(error) 50 | this.status.isLoadingData = false 51 | return Promise.reject(error) 52 | } 53 | }, 54 | /** 55 | * @description 发送数据 56 | * @param {Function} fn 请求函数 需要返回 Promise 57 | */ 58 | async doSubmit (fn = () => {}) { 59 | this.status.isSubmitting = true 60 | try { 61 | const data = await fn() 62 | this.status.isSubmitting = false 63 | return Promise.resolve(data) 64 | } catch (error) { 65 | console.log(error) 66 | this.status.isSubmitting = false 67 | return Promise.reject(error) 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/store/modules/d2admin/modules/size.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import router from '@/router' 3 | 4 | export default context => ({ 5 | namespaced: true, 6 | state: { 7 | // 尺寸 8 | value: '' // medium small mini 9 | }, 10 | actions: { 11 | /** 12 | * @description 将当前的设置应用到 element 13 | * @param {Object} vuex context 14 | * @param {Boolean} refresh 是否在设置之后刷新页面 15 | */ 16 | apply ({ state, commit }, refresh) { 17 | Vue.prototype.$ELEMENT.size = state.value 18 | if (refresh) { 19 | commit('d2admin/page/keepAliveClean', undefined, { root: true }) 20 | router.replace('/refresh') 21 | } 22 | }, 23 | /** 24 | * @description 确认已经加载组件尺寸设置 https://github.com/d2-projects/d2-admin/issues/198 25 | * @param {Object} vuex context 26 | */ 27 | isLoaded ({ state }) { 28 | if (state.value) return Promise.resolve() 29 | return new Promise(resolve => { 30 | const timer = setInterval(() => { 31 | if (state.value) { 32 | resolve(clearInterval(timer)) 33 | } 34 | }, 10) 35 | }) 36 | }, 37 | /** 38 | * @description 设置尺寸 39 | * @param {Object} vuex context 40 | * @param {String} size 尺寸 41 | */ 42 | async set ({ state, dispatch }, size) { 43 | // store 赋值 44 | state.value = size 45 | // 应用 46 | dispatch('apply', true) 47 | // 持久化 48 | await dispatch('d2admin/db/set', { 49 | dbName: 'sys', 50 | path: 'size.value', 51 | value: state.value, 52 | user: true 53 | }, { root: true }) 54 | }, 55 | /** 56 | * @description 从持久化数据读取尺寸设置 57 | * @param {Object} vuex context 58 | */ 59 | async load ({ state, dispatch }) { 60 | // store 赋值 61 | state.value = await dispatch('d2admin/db/get', { 62 | dbName: 'sys', 63 | path: 'size.value', 64 | defaultValue: 'mini', 65 | user: true 66 | }, { root: true }) 67 | // 应用 68 | dispatch('apply') 69 | } 70 | } 71 | }) 72 | -------------------------------------------------------------------------------- /src/store/modules/d2admin/modules/log.js: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs' 2 | import { get } from 'lodash' 3 | import utils from '@/utils' 4 | 5 | export default context => ({ 6 | namespaced: true, 7 | state: { 8 | // 错误日志 9 | // + 日志条目的属性 10 | // - message 必须 日志信息 11 | // - type 非必须 类型 success | warning | info(默认) | danger 12 | // - time 必须 日志记录时间 13 | // - meta 非必须 其它携带信息 14 | log: [] 15 | }, 16 | getters: { 17 | /** 18 | * @description 返回现存 log (all) 的条数 19 | * @param {*} state vuex state 20 | */ 21 | length (state) { 22 | return state.log.length 23 | }, 24 | /** 25 | * @description 返回现存 log (error) 的条数 26 | * @param {*} state vuex state 27 | */ 28 | lengthError (state) { 29 | return state.log.filter(log => log.type === 'danger').length 30 | } 31 | }, 32 | actions: { 33 | /** 34 | * @description 添加一个日志 35 | * @param {Object} vuex context 36 | * @param {String} param message {String} 信息 37 | * @param {String} param type {String} 类型 38 | * @param {Object} payload meta {Object} 附带的信息 39 | */ 40 | push ({ rootState, commit }, { message, type = 'info', meta }) { 41 | commit('push', { 42 | message, 43 | type, 44 | time: dayjs().format('YYYY-MM-DD HH:mm:ss'), 45 | meta: { 46 | // 当前用户信息 47 | user: rootState.d2admin.user.info, 48 | // 当前用户的 uuid 49 | uuid: utils.cookies.get('uuid'), 50 | // 当前的 token 51 | token: utils.cookies.get('token'), 52 | // 当前地址 53 | url: get(window, 'location.href', ''), 54 | // 用户设置 55 | ...meta 56 | } 57 | }) 58 | } 59 | }, 60 | mutations: { 61 | /** 62 | * @description 添加日志 63 | * @param {Object} state state 64 | * @param {Object} log data 65 | */ 66 | push (state, log) { 67 | state.log.push(log) 68 | }, 69 | /** 70 | * @description 清空日志 71 | * @param {Object} state state 72 | */ 73 | clean (state) { 74 | // store 赋值 75 | state.log = [] 76 | } 77 | } 78 | }) 79 | -------------------------------------------------------------------------------- /src/assets/style/public-class.scss: -------------------------------------------------------------------------------- 1 | @import 'public'; 2 | 3 | // 补丁 base 4 | @import '~@/assets/style/fixed/base.scss'; 5 | // 补丁 element 6 | @import '~@/assets/style/fixed/element.scss'; 7 | 8 | // 动画 9 | @import '~@/assets/style/animate/vue-transition.scss'; 10 | 11 | // 在这里写公用的class 12 | // 注意 这个文件里只写class 13 | // mixin等内容请在 public.scss 里书写 14 | 15 | // 文字相关 16 | .#{$prefix}-text-center { 17 | text-align: center; 18 | } 19 | 20 | // 浮动相关 21 | .#{$prefix}-fl { 22 | float: left; 23 | } 24 | .#{$prefix}-fr { 25 | float: right; 26 | } 27 | 28 | // 边距相关 29 | $sizes: (0, 5, 10, 15, 20); 30 | 31 | @for $index from 1 to 6 { 32 | .#{$prefix}-m-#{nth($sizes, $index)} { margin: #{nth($sizes, $index)}px !important; } 33 | .#{$prefix}-mt-#{nth($sizes, $index)} { margin-top: #{nth($sizes, $index)}px !important; } 34 | .#{$prefix}-mr-#{nth($sizes, $index)} { margin-right: #{nth($sizes, $index)}px !important; } 35 | .#{$prefix}-mb-#{nth($sizes, $index)} { margin-bottom: #{nth($sizes, $index)}px !important; } 36 | .#{$prefix}-ml-#{nth($sizes, $index)} { margin-left: #{nth($sizes, $index)}px !important; } 37 | 38 | .#{$prefix}-p-#{nth($sizes, $index)} { padding: #{nth($sizes, $index)}px !important; } 39 | .#{$prefix}-pt-#{nth($sizes, $index)} { padding-top: #{nth($sizes, $index)}px !important; } 40 | .#{$prefix}-pr-#{nth($sizes, $index)} { padding-right: #{nth($sizes, $index)}px !important; } 41 | .#{$prefix}-pb-#{nth($sizes, $index)} { padding-bottom: #{nth($sizes, $index)}px !important; } 42 | .#{$prefix}-pl-#{nth($sizes, $index)} { padding-left: #{nth($sizes, $index)}px !important; } 43 | } 44 | 45 | // 快速使用 46 | 47 | .#{$prefix}-m { margin: 20px !important; } 48 | .#{$prefix}-mt { margin-top: 20px !important; } 49 | .#{$prefix}-mr { margin-right: 20px !important; } 50 | .#{$prefix}-mb { margin-bottom: 20px !important; } 51 | .#{$prefix}-ml { margin-left: 20px !important; } 52 | 53 | .#{$prefix}-p { padding: 20px !important; } 54 | .#{$prefix}-pt { padding-top: 20px !important; } 55 | .#{$prefix}-pr { padding-right: 20px !important; } 56 | .#{$prefix}-pb { padding-bottom: 20px !important; } 57 | .#{$prefix}-pl { padding-left: 20px !important; } 58 | 59 | @for $index from 1 to 9 { 60 | .#{$prefix}-opacity-#{$index} { 61 | opacity: $index / 10; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/api/modules/menu.js: -------------------------------------------------------------------------------- 1 | import utils from '@/utils' 2 | 3 | export default ({ request }) => ({ 4 | /** 5 | * @description 菜单列表 只获取某级菜单下的数据 6 | * @description http://yapi.xiya.vip/project/11/interface/api/190 7 | * @param {Object} data {Number} parent 父级菜单 id 8 | */ 9 | MENU_FIND (id = 0) { 10 | return request({ 11 | url: '/api/menu/find_menus', 12 | method: 'post', 13 | data: { 14 | parent_id: id 15 | } 16 | }) 17 | }, 18 | /** 19 | * @description 菜单列表 20 | * @description 获取所有菜单的层级关系 21 | * @description http://yapi.xiya.vip/project/11/interface/api/170 22 | */ 23 | MENU_ALL (query = {}) { 24 | return request({ 25 | url: '/api/menu/menus', 26 | method: 'post', 27 | data: query 28 | }) 29 | }, 30 | /** 31 | * @description 菜单列表 32 | * @description 当前用的的菜单列表 33 | * @description http://yapi.xiya.vip/project/11/interface/api/560 34 | */ 35 | MENU_USER (userId) { 36 | return request({ 37 | url: '/api/menu/find_all_menu', 38 | method: 'post', 39 | data: { 40 | uuid: userId 41 | } 42 | }) 43 | }, 44 | /** 45 | * @description 菜单创建 46 | * @description http://yapi.xiya.vip/project/11/interface/api/100 47 | */ 48 | MENU_CREATE (data) { 49 | return request({ 50 | url: '/api/menu/create', 51 | method: 'put', 52 | data 53 | }) 54 | }, 55 | /** 56 | * @description 菜单详情 57 | * @description http://yapi.xiya.vip/project/11/interface/api/165 58 | */ 59 | MENU_DETAIL (id) { 60 | return request({ 61 | url: '/api/menu/update', 62 | method: 'post', 63 | data: { 64 | id 65 | } 66 | }) 67 | }, 68 | /** 69 | * @description 菜单编辑 70 | * @description http://yapi.xiya.vip/project/11/interface/api/165 71 | */ 72 | MENU_UPDATE (data) { 73 | return request({ 74 | url: '/api/menu/update', 75 | method: 'put', 76 | data 77 | }) 78 | }, 79 | /** 80 | * @description 菜单删除 81 | * @description http://yapi.xiya.vip/project/11/interface/api/110 82 | */ 83 | MENU_DELETE (id) { 84 | return request({ 85 | url: '/api/menu/delete', 86 | method: 'delete', 87 | data: { 88 | id 89 | } 90 | }) 91 | } 92 | }) 93 | -------------------------------------------------------------------------------- /src/mixins/crud-dict.js: -------------------------------------------------------------------------------- 1 | import { mapMutations } from 'vuex' 2 | 3 | export default { 4 | data () { 5 | return { 6 | status: { 7 | isLoadingDict: false 8 | } 9 | } 10 | }, 11 | methods: { 12 | ...mapMutations('d2admin/dict', { 13 | dictSet: 'set' 14 | }), 15 | /** 16 | * @description 加载需要的字典数据 17 | * @description 这个方法一般在页面上需要再次实现 18 | * @description 在此方法内使用 loadDictOne 19 | * @description loadDict 会自动以 doLoadDict(loadDict) 的形式被调用 20 | * @description doLoadDict -> loadDict -> loadDictOne 21 | * @description 在 d2-dict-x 类组件上使用的普通的字典会自动加载 无需再次使用此方法 22 | */ 23 | loadDict () {}, 24 | /** 25 | * @description 加载一个字典 26 | * @param {Object} config {String} name 字典名称 27 | * @param {Object} config {Function} method 请求方法 28 | * @param {Object} config {String} fields 需要的字段 29 | * @param {Object} config {Object} query 请求参数 30 | * @param {Object} config {String} path 返回数据的列表字段位置 31 | * @param {Object} config {String} label 字典 label 字段名 32 | * @param {Object} config {String} value 字典 value 字段名 33 | */ 34 | async loadDictOne ({ 35 | name = '', 36 | method = () => {}, 37 | fields = '', 38 | query = {}, 39 | path = '', 40 | label = 'label', 41 | value = 'id' 42 | }) { 43 | try { 44 | const result = await method( 45 | Object.assign( 46 | { page_size: 9999 }, 47 | fields ? { fields } : {}, 48 | query 49 | ) 50 | ) 51 | let dictValue = (path ? this._.get(result, path, []) : result).map(e => ({ label: e[label], value: e[value] })) 52 | this.dictSet({ name, value: dictValue }) 53 | } catch (error) { 54 | console.log(error) 55 | } 56 | }, 57 | /** 58 | * @description 请求字典数据 59 | * @param {Function} fn 请求函数 需要返回 Promise 60 | */ 61 | async doLoadDict (fn = () => {}) { 62 | this.status.isLoadingDict = true 63 | try { 64 | const data = await fn() 65 | this.status.isLoadingDict = false 66 | return Promise.resolve(data) 67 | } catch (error) { 68 | console.log(error) 69 | this.status.isLoadingDict = false 70 | return Promise.reject(error) 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/store/modules/d2admin/modules/theme.js: -------------------------------------------------------------------------------- 1 | // 设置文件 2 | import setting from '@/setting.js' 3 | 4 | export default context => ({ 5 | namespaced: true, 6 | state: { 7 | // 主题 8 | list: setting.theme.list, 9 | // 现在激活的主题 这应该是一个名字 不是对象 10 | activeName: setting.theme.list[0].name 11 | }, 12 | getters: { 13 | /** 14 | * @description 返回当前的主题信息 不是一个名字 而是当前激活主题的所有数据 15 | * @param {Object} state state 16 | */ 17 | activeSetting (state) { 18 | return state.list.find(theme => theme.name === state.activeName) 19 | } 20 | }, 21 | actions: { 22 | /** 23 | * @description 激活一个主题 24 | * @param {String} themeValue 需要激活的主题名称 25 | */ 26 | async set ({ state, commit, dispatch }, themeName) { 27 | // 检查这个主题在主题列表里是否存在 28 | state.activeName = state.list.find(e => e.name === themeName) ? themeName : state.list[0].name 29 | // 将 vuex 中的主题应用到 dom 30 | commit('dom') 31 | // 持久化 32 | await dispatch('d2admin/db/set', { 33 | dbName: 'sys', 34 | path: 'theme.activeName', 35 | value: state.activeName, 36 | user: true 37 | }, { root: true }) 38 | }, 39 | /** 40 | * @description 从持久化数据加载主题设置 41 | * @param {Object} vuex context 42 | */ 43 | async load ({ state, commit, dispatch }) { 44 | // store 赋值 45 | let activeName = await dispatch('d2admin/db/get', { 46 | dbName: 'sys', 47 | path: 'theme.activeName', 48 | defaultValue: state.list[0].name, 49 | user: true 50 | }, { root: true }) 51 | // 检查这个主题在主题列表里是否存在 52 | if (state.list.find(e => e.name === activeName)) { 53 | state.activeName = activeName 54 | } else { 55 | state.activeName = state.list[0].name 56 | // 持久化 57 | await dispatch('d2admin/db/set', { 58 | dbName: 'sys', 59 | path: 'theme.activeName', 60 | value: state.activeName, 61 | user: true 62 | }, { root: true }) 63 | } 64 | // 将 vuex 中的主题应用到 dom 65 | commit('dom') 66 | } 67 | }, 68 | mutations: { 69 | /** 70 | * @description 将 vuex 中的主题应用到 dom 71 | * @param {Object} state state 72 | */ 73 | dom (state) { 74 | document.body.className = `theme-${state.activeName}` 75 | } 76 | } 77 | }) 78 | -------------------------------------------------------------------------------- /src/store/modules/d2admin/modules/dict.js: -------------------------------------------------------------------------------- 1 | import { throttle } from 'lodash' 2 | import api from '@/api' 3 | 4 | const doFetch = throttle(async function ({ state, commit }) { 5 | const names = Array.from(new Set(state.fetchList)) 6 | commit('fetchListClean') 7 | const dictsData = await Promise.all(names.map(name => api.DICTDATA_ALL({ 8 | page_size: 9999, 9 | dict_type: name 10 | }))) 11 | dictsData.forEach((dictDataSource, dictDataIndex) => { 12 | const dictName = names[dictDataIndex] 13 | let dictData = [] 14 | if (dictDataSource.list.length > 0) { 15 | dictData = dictDataSource.list.map(e => ({ 16 | label: e.dict_label, 17 | value: e[dictDataSource.list[0].dict_value === '' ? 'dict_number' : 'dict_value'] 18 | })) 19 | } 20 | commit('set', { 21 | name: dictName, 22 | value: dictData 23 | }) 24 | }) 25 | }, 1000, { 26 | leading: false 27 | }) 28 | 29 | export default context => ({ 30 | namespaced: true, 31 | state: { 32 | // 字典数据 33 | dicts: [], 34 | // 请求队列 35 | fetchList: [] 36 | }, 37 | actions: { 38 | /** 39 | * @description 获取字典 40 | * @param {Object} vuex context 41 | * @param {String} name 字典名称 42 | */ 43 | get ({ state }, name) { 44 | const dict = state.dicts.find(e => e.name === name) 45 | return (dict && dict.value) || [] 46 | }, 47 | /** 48 | * @description 加载一个字典 49 | * @param {Object} vuex context 50 | * @param {String} name 字典名称 51 | */ 52 | fetch (payload, name = '') { 53 | payload.commit('fetchListPush', name) 54 | doFetch(payload) 55 | } 56 | }, 57 | mutations: { 58 | /** 59 | * @description 设置字典 60 | * @param {Object} vuex context 61 | * @param {Object} payload {String} name 字典名称 62 | * @param {Object} payload {Array} value 字典数据 63 | */ 64 | set (state, { name = '', value = [] } = {}) { 65 | const dictIndex = state.dicts.findIndex(e => e.name === name) 66 | if (dictIndex < 0) { 67 | state.dicts.push({ name, value }) 68 | } else { 69 | state.dicts.splice(dictIndex, 1, { name, value }) 70 | } 71 | }, 72 | fetchListPush (state, item) { 73 | state.fetchList.push(item) 74 | }, 75 | fetchListClean (state) { 76 | state.fetchList = [] 77 | } 78 | } 79 | }) 80 | -------------------------------------------------------------------------------- /src/assets/style/theme/star/setting.scss: -------------------------------------------------------------------------------- 1 | // 主题名称 2 | $theme-name: 'star'; 3 | // 主题背景颜色 4 | $theme-bg-color: #EFF4F8; 5 | // 主题背景图片遮罩 6 | $theme-bg-mask: rgba(#000, .3); 7 | 8 | // container组件 9 | $theme-container-background-color: rgba(#FFF, .9); 10 | $theme-container-header-footer-background-color: #FFF; 11 | $theme-container-border-inner: 1px solid $color-border-1; 12 | $theme-container-border-outer: 1px solid #114450; 13 | 14 | $theme-multiple-page-control-color: #FFF; 15 | $theme-multiple-page-control-color-active: $color-text-normal; 16 | $theme-multiple-page-control-nav-prev-color: #FFF; 17 | $theme-multiple-page-control-nav-next-color: #FFF; 18 | $theme-multiple-page-control-border-color: #114450; 19 | $theme-multiple-page-control-border-color-active: #FFF; 20 | $theme-multiple-page-control-background-color: rgba(#FFF, .5); 21 | $theme-multiple-page-control-background-color-active: #FFF; 22 | 23 | // 顶栏和侧边栏中展开的菜单 hover 状态下 24 | $theme-menu-item-color-hover: #293849; 25 | $theme-menu-item-background-color-hover: #ecf5ff; 26 | 27 | // 顶栏上的文字颜色 28 | $theme-header-item-color: #FFF; 29 | $theme-header-item-background-color: transparent; 30 | // 顶栏上的项目在 hover 时 31 | $theme-header-item-color-hover: #FFF; 32 | $theme-header-item-background-color-hover: rgba(#000, .2); 33 | // 顶栏上的项目在 focus 时 34 | $theme-header-item-color-focus: #FFF; 35 | $theme-header-item-background-color-focus: rgba(#000, .2); 36 | // 顶栏上的项目在 active 时 37 | $theme-header-item-color-active: #FFF; 38 | $theme-header-item-background-color-active: rgba(#000, .3); 39 | 40 | // 侧边栏上的文字颜色 41 | $theme-aside-item-color: #FFF; 42 | $theme-aside-item-background-color: transparent; 43 | // 侧边栏上的项目在 hover 时 44 | $theme-aside-item-color-hover: #FFF; 45 | $theme-aside-item-background-color-hover: rgba(#000, .2); 46 | // 侧边栏上的项目在 focus 时 47 | $theme-aside-item-color-focus: #FFF; 48 | $theme-aside-item-background-color-focus: rgba(#000, .2); 49 | // 侧边栏上的项目在 active 时 50 | $theme-aside-item-color-active: #FFF; 51 | $theme-aside-item-background-color-active: rgba(#000, .3); 52 | 53 | // 侧边栏菜单为空的时候显示的元素 54 | $theme-aside-menu-empty-icon-color: #FFF; 55 | $theme-aside-menu-empty-text-color: #FFF; 56 | $theme-aside-menu-empty-background-color: rgba(#FFF, .2); 57 | $theme-aside-menu-empty-icon-color-hover: #FFF; 58 | $theme-aside-menu-empty-text-color-hover: #FFF; 59 | $theme-aside-menu-empty-background-color-hover: rgba(#FFF, .3); -------------------------------------------------------------------------------- /src/api/modules/user.js: -------------------------------------------------------------------------------- 1 | export default ({ request }) => ({ 2 | /** 3 | * @description 登录 4 | * @description http://yapi.xiya.vip/project/11/interface/api/10 5 | */ 6 | USER_LOGIN ({ 7 | username = '', 8 | password = '' 9 | }) { 10 | return request({ 11 | url: '/api/user/login', 12 | method: 'post', 13 | data: { 14 | username: username, 15 | password: password 16 | } 17 | }) 18 | }, 19 | /** 20 | * @description 注销 21 | * @description http://yapi.xiya.vip/project/11/interface/api/40 22 | */ 23 | USER_LOGOUT () { 24 | return request({ 25 | url: '/api/user/logout', 26 | method: 'post' 27 | }) 28 | }, 29 | /** 30 | * @description Token 校验 31 | * @description http://yapi.xiya.vip/project/11/interface/api/15 32 | */ 33 | USER_CHECK_TOKEN () { 34 | return request({ 35 | url: '/api/user/check_token', 36 | method: 'post' 37 | }) 38 | }, 39 | /** 40 | * @description 查询所有用户 41 | * @description http://yapi.xiya.vip/project/11/interface/api/30 42 | */ 43 | USER_ALL (query = {}) { 44 | return request({ 45 | url: '/api/user/index', 46 | method: 'post', 47 | data: query 48 | }) 49 | }, 50 | /** 51 | * @description 用户创建 52 | * @description http://yapi.xiya.vip/project/11/interface/api/25 53 | */ 54 | USER_CREATE (data) { 55 | return request({ 56 | url: '/api/user/create', 57 | method: 'put', 58 | data 59 | }) 60 | }, 61 | /** 62 | * @description 用户详情 63 | * @description http://yapi.xiya.vip/project/11/interface/api/240 64 | */ 65 | USER_DETAIL (id) { 66 | return request({ 67 | url: '/api/user/update', 68 | method: 'post', 69 | data: { 70 | id 71 | } 72 | }) 73 | }, 74 | /** 75 | * @description 用户编辑 76 | * @description http://yapi.xiya.vip/project/11/interface/api/240 77 | */ 78 | USER_UPDATE (data) { 79 | return request({ 80 | url: '/api/user/update', 81 | method: 'put', 82 | data 83 | }) 84 | }, 85 | /** 86 | * @description 用户删除 87 | * @description http://yapi.xiya.vip/project/11/interface/api/155 88 | */ 89 | USER_DELETE (id) { 90 | return request({ 91 | url: '/api/user/delete', 92 | method: 'delete', 93 | data: { 94 | id 95 | } 96 | }) 97 | } 98 | }) 99 | -------------------------------------------------------------------------------- /src/assets/style/theme/tomorrow-night-blue/setting.scss: -------------------------------------------------------------------------------- 1 | // 主题名称 2 | $theme-name: 'tomorrow-night-blue'; 3 | // 主题背景颜色 4 | $theme-bg-color: #002253; 5 | // 主题背景图片遮罩 6 | $theme-bg-mask: rgba(#000, 0); 7 | 8 | // container组件 9 | $theme-container-background-color: rgba(#FFF, 1); 10 | $theme-container-header-footer-background-color: #FFF; 11 | $theme-container-border-inner: 1px solid $color-border-1; 12 | $theme-container-border-outer: 1px solid #002253; 13 | 14 | $theme-multiple-page-control-color: #FFF; 15 | $theme-multiple-page-control-color-active: $color-text-normal; 16 | $theme-multiple-page-control-nav-prev-color: #FFF; 17 | $theme-multiple-page-control-nav-next-color: #FFF; 18 | $theme-multiple-page-control-border-color: #002253; 19 | $theme-multiple-page-control-border-color-active: #FFF; 20 | $theme-multiple-page-control-background-color: rgba(#FFF, .2); 21 | $theme-multiple-page-control-background-color-active: #FFF; 22 | 23 | // 顶栏和侧边栏中展开的菜单 hover 状态下 24 | $theme-menu-item-color-hover: #293849; 25 | $theme-menu-item-background-color-hover: #ecf5ff; 26 | 27 | // 顶栏上的文字颜色 28 | $theme-header-item-color: #FF929A; 29 | $theme-header-item-background-color: transparent; 30 | // 顶栏上的项目在 hover 时 31 | $theme-header-item-color-hover: #FFEBA4; 32 | $theme-header-item-background-color-hover: rgba(#FFF, .05); 33 | // 顶栏上的项目在 focus 时 34 | $theme-header-item-color-focus: #FFB870; 35 | $theme-header-item-background-color-focus: rgba(#FFF, .05); 36 | // 顶栏上的项目在 active 时 37 | $theme-header-item-color-active: #FFB870; 38 | $theme-header-item-background-color-active: rgba(#FFF, .05); 39 | 40 | // 侧边栏上的文字颜色 41 | $theme-aside-item-color: #FF929A; 42 | $theme-aside-item-background-color: transparent; 43 | // 侧边栏上的项目在 hover 时 44 | $theme-aside-item-color-hover: #FFEBA4; 45 | $theme-aside-item-background-color-hover: rgba(#FFF, .05); 46 | // 侧边栏上的项目在 focus 时 47 | $theme-aside-item-color-focus: #FFB870; 48 | $theme-aside-item-background-color-focus: rgba(#FFF, .05); 49 | // 侧边栏上的项目在 active 时 50 | $theme-aside-item-color-active: #FFB870; 51 | $theme-aside-item-background-color-active: rgba(#FFF, .05); 52 | 53 | // 侧边栏菜单为空的时候显示的元素 54 | $theme-aside-menu-empty-icon-color: #FFB870; 55 | $theme-aside-menu-empty-text-color: #FFB870; 56 | $theme-aside-menu-empty-background-color: rgba(#FFF, .1); 57 | $theme-aside-menu-empty-icon-color-hover: #FFEBA4; 58 | $theme-aside-menu-empty-text-color-hover: #FFEBA4; 59 | $theme-aside-menu-empty-background-color-hover: rgba(#FFF, .2); -------------------------------------------------------------------------------- /src/views/management/dept/form.js: -------------------------------------------------------------------------------- 1 | import form from '@/mixins/crud-form' 2 | import utils from '@/utils/index' 3 | 4 | export default { 5 | mixins: [ form ], 6 | data () { 7 | return { 8 | api: { 9 | detail: 'DEPT_DETAIL', 10 | create: 'DEPT_CREATE', 11 | update: 'DEPT_UPDATE' 12 | } 13 | } 14 | }, 15 | computed: { 16 | setting () { 17 | return [ 18 | { 19 | prop: 'dept_name', 20 | default: '', 21 | label: '部门名称', 22 | rule: { required: true, message: '必填', trigger: 'change' }, 23 | render: () => 24 | }, 25 | { 26 | prop: 'parent_id', 27 | default: 0, 28 | label: '上级部门', 29 | render: () => 30 | }, 31 | { 32 | prop: 'leader', 33 | default: '', 34 | label: '负责人', 35 | render: () => 36 | }, 37 | { 38 | prop: 'email', 39 | default: '', 40 | label: '部门邮箱', 41 | rule: { validator: utils.helper.isLegalEmailValidator, trigger: 'change' }, 42 | render: () => 43 | }, 44 | { 45 | prop: 'phone', 46 | default: '', 47 | label: '部门电话', 48 | rule: { validator: utils.helper.isLegalMobilePhoneValidator, trigger: 'change' }, 49 | render: () => 50 | }, 51 | { 52 | prop: 'order_num', 53 | default: this.$env.VUE_APP_FORM_SORT_MIN, 54 | label: '显示排序', 55 | render: () => 56 | }, 57 | { 58 | prop: 'status', 59 | default: this.$env.VUE_APP_DICT_STATUS_ACTIVE, 60 | label: '状态', 61 | render: () => 62 | }, 63 | { 64 | prop: 'remark', 65 | default: '', 66 | label: '备注', 67 | render: () => 68 | } 69 | ] 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/assets/style/theme/d2/setting.scss: -------------------------------------------------------------------------------- 1 | // 主题名称 2 | $theme-name: 'd2'; 3 | // 主题背景颜色 4 | $theme-bg-color: #ebf1f6; 5 | // 主题背景图片遮罩 6 | $theme-bg-mask: rgba(#000, 0); 7 | 8 | // container组件 9 | $theme-container-background-color: rgba(#FFF, 1); 10 | $theme-container-header-footer-background-color: #FFF; 11 | $theme-container-border-inner: 1px solid #cfd7e5; 12 | $theme-container-border-outer: 1px solid #cfd7e5; 13 | 14 | $theme-multiple-page-control-color: $color-text-normal; 15 | $theme-multiple-page-control-color-active: #2f74ff; 16 | $theme-multiple-page-control-nav-prev-color: #cfd7e5; 17 | $theme-multiple-page-control-nav-next-color: #cfd7e5; 18 | $theme-multiple-page-control-border-color: #cfd7e5; 19 | $theme-multiple-page-control-border-color-active: #FFF; 20 | $theme-multiple-page-control-background-color: rgba(#000, .03); 21 | $theme-multiple-page-control-background-color-active: #FFF; 22 | 23 | // 顶栏和侧边栏中展开的菜单 hover 状态下 24 | $theme-menu-item-color-hover: #293849; 25 | $theme-menu-item-background-color-hover: #ecf5ff; 26 | 27 | // 顶栏上的文字颜色 28 | $theme-header-item-color: $color-text-normal; 29 | $theme-header-item-background-color: transparent; 30 | // 顶栏上的项目在 hover 时 31 | $theme-header-item-color-hover: #2f74ff; 32 | $theme-header-item-background-color-hover: rgba(#FFF, .5); 33 | // 顶栏上的项目在 focus 时 34 | $theme-header-item-color-focus: #2f74ff; 35 | $theme-header-item-background-color-focus: rgba(#FFF, .5); 36 | // 顶栏上的项目在 active 时 37 | $theme-header-item-color-active: #2f74ff; 38 | $theme-header-item-background-color-active: rgba(#FFF, .5); 39 | 40 | // 侧边栏上的文字颜色 41 | $theme-aside-item-color: $color-text-normal; 42 | $theme-aside-item-background-color: transparent; 43 | // 侧边栏上的项目在 hover 时 44 | $theme-aside-item-color-hover: #2f74ff; 45 | $theme-aside-item-background-color-hover: rgba(#FFF, .5); 46 | // 侧边栏上的项目在 focus 时 47 | $theme-aside-item-color-focus: #2f74ff; 48 | $theme-aside-item-background-color-focus: rgba(#FFF, .5); 49 | // 侧边栏上的项目在 active 时 50 | $theme-aside-item-color-active: #2f74ff; 51 | $theme-aside-item-background-color-active: rgba(#FFF, .5); 52 | 53 | // 侧边栏菜单为空的时候显示的元素 54 | $theme-aside-menu-empty-icon-color: $color-text-normal; 55 | $theme-aside-menu-empty-text-color: $color-text-normal; 56 | $theme-aside-menu-empty-background-color: rgba(#000, .03); 57 | $theme-aside-menu-empty-icon-color-hover: $color-text-main; 58 | $theme-aside-menu-empty-text-color-hover: $color-text-main; 59 | $theme-aside-menu-empty-background-color-hover: rgba(#000, .05); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "d2-admin-xiya-go-cms", 3 | "version": "0.0.2", 4 | "scripts": { 5 | "serve": "vue-cli-service serve --open", 6 | "start": "npm run serve", 7 | "dev": "npm run serve", 8 | "build": "vue-cli-service build", 9 | "build:preview": "NODE_OPTIONS=--max_old_space_size=4096 vue-cli-service build --mode preview", 10 | "build:github": "NODE_OPTIONS=--max_old_space_size=4096 vue-cli-service build --mode github", 11 | "build:netlify": "NODE_OPTIONS=--max_old_space_size=4096 vue-cli-service build --mode netlify", 12 | "lint": "vue-cli-service lint --fix", 13 | "test:unit": "vue-cli-service test:unit", 14 | "i18n:report": "vue-cli-service i18n:report --src './src/**/*.?(js|vue)' --locales './src/locales/**/*.json'", 15 | "analyz": "npm_config_report=true npm run build" 16 | }, 17 | "dependencies": { 18 | "axios": "^0.19.0", 19 | "better-scroll": "^1.15.2", 20 | "core-js": "^3.4.3", 21 | "dayjs": "^1.8.17", 22 | "element-ui": "^2.13.0", 23 | "flex.css": "^1.1.7", 24 | "fuse.js": "^3.4.6", 25 | "hotkeys-js": "^3.7.3", 26 | "js-cookie": "^2.2.1", 27 | "lodash": "^4.17.15", 28 | "lowdb": "^1.0.0", 29 | "nprogress": "^0.2.0", 30 | "qs": "^6.9.1", 31 | "quill": "^1.3.7", 32 | "screenfull": "^5.0.0", 33 | "shortid": "^2.2.15", 34 | "sortablejs": "^1.10.1", 35 | "ua-parser-js": "^0.7.20", 36 | "vue": "^2.6.11", 37 | "vue-i18n": "^8.15.1", 38 | "vue-router": "^3.1.3", 39 | "vuedraggable": "^2.23.2", 40 | "vuex": "^3.1.2" 41 | }, 42 | "devDependencies": { 43 | "@d2-projects/vue-filename-injector": "^1.1.0", 44 | "@kazupon/vue-i18n-loader": "^0.4.1", 45 | "@vue/cli-plugin-babel": "^4.1.0", 46 | "@vue/cli-plugin-eslint": "^4.1.0", 47 | "@vue/cli-plugin-router": "^4.1.0", 48 | "@vue/cli-plugin-unit-jest": "^4.1.0", 49 | "@vue/cli-plugin-vuex": "^4.1.0", 50 | "@vue/cli-service": "^4.1.0", 51 | "@vue/eslint-config-standard": "^4.0.0", 52 | "@vue/test-utils": "1.0.0-beta.29", 53 | "babel-eslint": "^10.0.3", 54 | "compression-webpack-plugin": "^3.0.1", 55 | "eslint": "^5.16.0", 56 | "eslint-plugin-vue": "^5.0.0", 57 | "sass": "^1.23.7", 58 | "sass-loader": "^8.0.0", 59 | "svg-sprite-loader": "^4.1.6", 60 | "text-loader": "^0.0.1", 61 | "vue-cli-plugin-i18n": "^0.6.0", 62 | "vue-template-compiler": "^2.6.10", 63 | "webpack-bundle-analyzer": "^3.6.0" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /dependencies-cdn.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { name: 'vue', library: 'Vue', js: 'https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js', css: '' }, 3 | { name: 'vue-i18n', library: 'VueI18n', js: 'https://cdn.jsdelivr.net/npm/vue-i18n@8.15.1/dist/vue-i18n.js', css: '' }, 4 | { name: 'vue-router', library: 'VueRouter', js: 'https://cdn.jsdelivr.net/npm/vue-router@3.1.3/dist/vue-router.js', css: '' }, 5 | { name: 'vuex', library: 'Vuex', js: 'https://cdn.jsdelivr.net/npm/vuex@3.1.2/dist/vuex.js', css: '' }, 6 | { name: 'axios', library: 'axios', js: 'https://cdn.jsdelivr.net/npm/axios@0.19.0/dist/axios.min.js', css: '' }, 7 | { name: 'better-scroll', library: 'BScroll', js: 'https://cdn.jsdelivr.net/npm/better-scroll@1.15.2/dist/bscroll.min.js', css: '' }, 8 | { name: 'element-ui', library: 'ELEMENT', js: 'https://cdn.jsdelivr.net/npm/element-ui@2.13.0/lib/index.js', css: 'https://cdn.jsdelivr.net/npm/element-ui@2.13.0/lib/theme-chalk/index.css' }, 9 | { name: 'lodash', library: '_', js: 'https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js', css: '' }, 10 | { name: 'ua-parser-js', library: 'UAParser', js: 'https://cdn.jsdelivr.net/npm/ua-parser-js@0.7.20/dist/ua-parser.min.js', css: '' }, 11 | { name: 'js-cookie', library: 'Cookies', js: 'https://cdn.jsdelivr.net/npm/js-cookie@2.2.1/src/js.cookie.min.js', css: '' }, 12 | { name: 'nprogress', library: 'NProgress', js: 'https://cdn.jsdelivr.net/npm/nprogress@0.2.0/nprogress.min.js', css: 'https://cdn.jsdelivr.net/npm/nprogress@0.2.0/nprogress.css' }, 13 | { name: 'dayjs', library: 'dayjs', js: 'https://cdn.jsdelivr.net/npm/dayjs@1.8.17/dayjs.min.js', css: '' }, 14 | { name: 'fuse.js', library: 'Fuse', js: 'https://cdn.jsdelivr.net/npm/fuse.js@3.4.6/dist/fuse.min.js', css: '' }, 15 | { name: 'hotkeys-js', library: 'hotkeys', js: 'https://cdn.jsdelivr.net/npm/hotkeys-js@3.7.3/dist/hotkeys.js', css: '' }, 16 | { name: 'qs', library: 'Qs', js: 'https://cdn.jsdelivr.net/npm/qs@6.9.1/dist/qs.js', css: '' }, 17 | { name: 'lowdb', library: 'low', js: 'https://cdn.jsdelivr.net/npm/lowdb@1.0.0/dist/low.min.js', css: '' }, 18 | { name: 'lowdb/adapters/LocalStorage', library: 'LocalStorage', js: 'https://cdn.jsdelivr.net/npm/lowdb@1.0.0/dist/LocalStorage.min.js', css: '' }, 19 | { name: 'screenfull', library: 'screenfull', js: 'https://cdn.jsdelivr.net/npm/screenfull@5.0.0/dist/screenfull.min.js', css: '' }, 20 | { name: 'sortablejs', library: 'Sortable', js: 'https://cdn.jsdelivr.net/npm/sortablejs@1.10.1/Sortable.min.js', css: '' } 21 | ] 22 | -------------------------------------------------------------------------------- /src/assets/style/theme/line/setting.scss: -------------------------------------------------------------------------------- 1 | // 主题名称 2 | $theme-name: 'line'; 3 | // 主题背景颜色 4 | $theme-bg-color: #f8f8f9; 5 | // 主题背景图片遮罩 6 | $theme-bg-mask: rgba(#000, 0); 7 | 8 | // container组件 9 | $theme-container-background-color: rgba(#FFF, .8); 10 | $theme-container-header-footer-background-color: #FFF; 11 | $theme-container-border-inner: 1px solid $color-border-2; 12 | $theme-container-border-outer: 1px solid #cfd7e5; 13 | 14 | $theme-multiple-page-control-color: #FFF; 15 | $theme-multiple-page-control-color-active: $color-text-normal; 16 | $theme-multiple-page-control-nav-prev-color: #cfd7e5; 17 | $theme-multiple-page-control-nav-next-color: #cfd7e5; 18 | $theme-multiple-page-control-border-color: #cfd7e5; 19 | $theme-multiple-page-control-border-color-active: #FFF; 20 | $theme-multiple-page-control-background-color: #cfd7e5; 21 | $theme-multiple-page-control-background-color-active: #FFF; 22 | 23 | // 顶栏和侧边栏中展开的菜单 hover 状态下 24 | $theme-menu-item-color-hover: #293849; 25 | $theme-menu-item-background-color-hover: #EFEFEF; 26 | 27 | // 顶栏上的文字颜色 28 | $theme-header-item-color: $color-text-normal; 29 | $theme-header-item-background-color: transparent; 30 | // 顶栏上的项目在 hover 时 31 | $theme-header-item-color-hover: $color-text-main; 32 | $theme-header-item-background-color-hover: rgba(#000, .02); 33 | // 顶栏上的项目在 focus 时 34 | $theme-header-item-color-focus: $color-text-main; 35 | $theme-header-item-background-color-focus: rgba(#000, .02); 36 | // 顶栏上的项目在 active 时 37 | $theme-header-item-color-active: $color-text-main; 38 | $theme-header-item-background-color-active: rgba(#000, .03); 39 | 40 | // 侧边栏上的文字颜色 41 | $theme-aside-item-color: $color-text-normal; 42 | $theme-aside-item-background-color: transparent; 43 | // 侧边栏上的项目在 hover 时 44 | $theme-aside-item-color-hover: $color-text-main; 45 | $theme-aside-item-background-color-hover: rgba(#000, .02); 46 | // 侧边栏上的项目在 focus 时 47 | $theme-aside-item-color-focus: $color-text-main; 48 | $theme-aside-item-background-color-focus: rgba(#000, .02); 49 | // 侧边栏上的项目在 active 时 50 | $theme-aside-item-color-active: $color-text-main; 51 | $theme-aside-item-background-color-active: rgba(#000, .03); 52 | 53 | // 侧边栏菜单为空的时候显示的元素 54 | $theme-aside-menu-empty-icon-color: $color-text-normal; 55 | $theme-aside-menu-empty-text-color: $color-text-normal; 56 | $theme-aside-menu-empty-background-color: rgba(#000, .03); 57 | $theme-aside-menu-empty-icon-color-hover: $color-text-main; 58 | $theme-aside-menu-empty-text-color-hover: $color-text-main; 59 | $theme-aside-menu-empty-background-color-hover: rgba(#000, .05); -------------------------------------------------------------------------------- /src/assets/svg-icons/icons/d2-admin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | D2Admin 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/store/modules/d2admin/modules/menu.js: -------------------------------------------------------------------------------- 1 | import { uniqueId } from 'lodash' 2 | // 设置文件 3 | import setting from '@/setting.js' 4 | 5 | /** 6 | * 给菜单数据补充上 path 字段 7 | * https://github.com/d2-projects/d2-admin/issues/209 8 | * @param {Array} menu 原始的菜单数据 9 | */ 10 | function supplementMenuPath (menu) { 11 | return menu.map(e => ({ 12 | ...e, 13 | path: e.path || uniqueId('d2-menu-empty-'), 14 | ...e.children ? { 15 | children: supplementMenuPath(e.children) 16 | } : {} 17 | })) 18 | } 19 | 20 | export default context => ({ 21 | namespaced: true, 22 | state: { 23 | // 顶栏菜单 24 | header: [], 25 | // 侧栏菜单 26 | aside: [], 27 | // 侧边栏收缩 28 | asideCollapse: setting.menu.asideCollapse 29 | }, 30 | actions: { 31 | /** 32 | * 设置侧边栏展开或者收缩 33 | * @param {Object} vuex context 34 | * @param {Boolean} collapse is collapse 35 | */ 36 | async asideCollapseSet ({ state, dispatch }, collapse) { 37 | // store 赋值 38 | state.asideCollapse = collapse 39 | // 持久化 40 | await dispatch('d2admin/db/set', { 41 | dbName: 'sys', 42 | path: 'menu.asideCollapse', 43 | value: state.asideCollapse, 44 | user: true 45 | }, { root: true }) 46 | }, 47 | /** 48 | * 切换侧边栏展开和收缩 49 | * @param {Object} vuex context 50 | */ 51 | async asideCollapseToggle ({ state, dispatch }) { 52 | // store 赋值 53 | state.asideCollapse = !state.asideCollapse 54 | // 持久化 55 | await dispatch('d2admin/db/set', { 56 | dbName: 'sys', 57 | path: 'menu.asideCollapse', 58 | value: state.asideCollapse, 59 | user: true 60 | }, { root: true }) 61 | }, 62 | /** 63 | * 从持久化数据读取侧边栏展开或者收缩 64 | * @param {Object} vuex context 65 | */ 66 | async asideCollapseLoad ({ state, dispatch }) { 67 | // store 赋值 68 | state.asideCollapse = await dispatch('d2admin/db/get', { 69 | dbName: 'sys', 70 | path: 'menu.asideCollapse', 71 | defaultValue: setting.menu.asideCollapse, 72 | user: true 73 | }, { root: true }) 74 | } 75 | }, 76 | mutations: { 77 | /** 78 | * @description 设置顶栏菜单 79 | * @param {Object} state state 80 | * @param {Array} menu menu setting 81 | */ 82 | headerSet (state, menu) { 83 | // store 赋值 84 | state.header = supplementMenuPath(menu) 85 | }, 86 | /** 87 | * @description 设置侧边栏菜单 88 | * @param {Object} state state 89 | * @param {Array} menu menu setting 90 | */ 91 | asideSet (state, menu) { 92 | // store 赋值 93 | state.aside = supplementMenuPath(menu) 94 | } 95 | } 96 | }) 97 | -------------------------------------------------------------------------------- /src/views/system/log/index.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 83 | -------------------------------------------------------------------------------- /src/assets/style/theme/violet/setting.scss: -------------------------------------------------------------------------------- 1 | // 主题名称 2 | $theme-name: 'violet'; 3 | // 主题背景颜色 4 | $theme-bg-color: #000; 5 | // 主题背景图片遮罩 6 | $theme-bg-mask: rgba(#000, 0); 7 | 8 | // container组件 9 | $theme-container-background-color: #FFF; 10 | $theme-container-header-footer-background-color: #FFF; 11 | $theme-container-border-inner: 1px solid $color-border-2; 12 | $theme-container-border-outer: 1px solid #8C40E2; 13 | 14 | $theme-multiple-page-control-color: #FFF; 15 | $theme-multiple-page-control-color-active: $color-text-normal; 16 | $theme-multiple-page-control-nav-prev-color: #FFF; 17 | $theme-multiple-page-control-nav-next-color: #FFF; 18 | $theme-multiple-page-control-border-color: #8C40E2; 19 | $theme-multiple-page-control-border-color-active: #FFF; 20 | $theme-multiple-page-control-background-color: rgba(#FFF, .3); 21 | $theme-multiple-page-control-background-color-active: #FFF; 22 | 23 | // 顶栏和侧边栏中展开的菜单 hover 状态下 24 | $theme-menu-item-color-hover: #293849; 25 | $theme-menu-item-background-color-hover: #ecf5ff; 26 | 27 | // 顶栏上的文字颜色 28 | $theme-header-item-color: #FFF; 29 | $theme-header-item-background-color: transparent; 30 | // 顶栏上的项目在 hover 时 31 | $theme-header-item-color-hover: #FFF; 32 | $theme-header-item-background-color-hover: linear-gradient(-180deg, rgba(255,255,255,0.18) 0%, rgba(255,255,255,0.12) 100%); 33 | // 顶栏上的项目在 focus 时 34 | $theme-header-item-color-focus: #FFF; 35 | $theme-header-item-background-color-focus: linear-gradient(-180deg, rgba(255,255,255,0.18) 0%, rgba(255,255,255,0.12) 100%); 36 | // 顶栏上的项目在 active 时 37 | $theme-header-item-color-active: #FFF; 38 | $theme-header-item-background-color-active: linear-gradient(-180deg, rgba(255,255,255,0.18) 0%, rgba(255,255,255,0.12) 100%); 39 | 40 | // 侧边栏上的文字颜色 41 | $theme-aside-item-color: #FFF; 42 | $theme-aside-item-background-color: transparent; 43 | // 侧边栏上的项目在 hover 时 44 | $theme-aside-item-color-hover: #FFF; 45 | $theme-aside-item-background-color-hover: linear-gradient(90deg, rgba(255,255,255,0.28) 0%, rgba(255,255,255,0.00) 100%); 46 | // 侧边栏上的项目在 focus 时 47 | $theme-aside-item-color-focus: #FFF; 48 | $theme-aside-item-background-color-focus: linear-gradient(90deg, rgba(255,255,255,0.28) 0%, rgba(255,255,255,0.00) 100%); 49 | // 侧边栏上的项目在 active 时 50 | $theme-aside-item-color-active: #FFF; 51 | $theme-aside-item-background-color-active: linear-gradient(90deg, rgba(255,255,255,0.28) 0%, rgba(255,255,255,0.00) 100%); 52 | 53 | // 侧边栏菜单为空的时候显示的元素 54 | $theme-aside-menu-empty-icon-color: #FFF; 55 | $theme-aside-menu-empty-text-color: #FFF; 56 | $theme-aside-menu-empty-background-color: rgba(#000, .1); 57 | $theme-aside-menu-empty-icon-color-hover: #FFF; 58 | $theme-aside-menu-empty-text-color-hover: #FFF; 59 | $theme-aside-menu-empty-background-color-hover: rgba(#000, .15); -------------------------------------------------------------------------------- /src/components/d2-search-panel/component.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 54 | 55 | 111 | -------------------------------------------------------------------------------- /src/utils/modules/db.js: -------------------------------------------------------------------------------- 1 | import low from 'lowdb' 2 | import LocalStorage from 'lowdb/adapters/LocalStorage' 3 | import * as cookies from './cookies' 4 | import { cloneDeep } from 'lodash' 5 | import env from '@/env' 6 | 7 | const adapter = new LocalStorage(`d2admin-${env.VUE_APP_VERSION}`) 8 | const db = low(adapter) 9 | 10 | db 11 | .defaults({ 12 | sys: {}, 13 | database: {} 14 | }) 15 | .write() 16 | 17 | /** 18 | * @description 检查路径是否存在 不存在的话初始化 19 | * @param {Object} payload dbName {String} 数据库名称 20 | * @param {Object} payload path {String} 路径 21 | * @param {Object} payload user {Boolean} 区分用户 22 | * @param {Object} payload validator {Function} 数据校验钩子 返回 true 表示验证通过 23 | * @param {Object} payload defaultValue {*} 初始化默认值 24 | * @returns {String} 可以直接使用的路径 25 | */ 26 | function pathInit ({ 27 | dbName = 'database', 28 | path = '', 29 | user = true, 30 | validator = () => true, 31 | defaultValue = '' 32 | }) { 33 | const uuid = cookies.get('uuid') || 'ghost-uuid' 34 | const currentPath = `${dbName}.${user ? `user.${uuid}` : 'public'}${path ? `.${path}` : ''}` 35 | const value = db.get(currentPath).value() 36 | if (!(value !== undefined && validator(value))) { 37 | db 38 | .set(currentPath, defaultValue) 39 | .write() 40 | } 41 | return currentPath 42 | } 43 | 44 | /** 45 | * @description 将数据存储到指定位置 | 路径不存在会自动初始化 46 | * @description 效果类似于取值 dbName.path = value 47 | * @param {Object} payload dbName {String} 数据库名称 48 | * @param {Object} payload path {String} 存储路径 49 | * @param {Object} payload value {*} 需要存储的值 50 | * @param {Object} payload user {Boolean} 是否区分用户 51 | */ 52 | export function dbSet ({ 53 | dbName = 'database', 54 | path = '', 55 | value = '', 56 | user = false 57 | }) { 58 | db.set(pathInit({ 59 | dbName, 60 | path, 61 | user 62 | }), value).write() 63 | } 64 | 65 | /** 66 | * @description 获取数据 67 | * @description 效果类似于取值 dbName.path || defaultValue 68 | * @param {Object} payload dbName {String} 数据库名称 69 | * @param {Object} payload path {String} 存储路径 70 | * @param {Object} payload defaultValue {*} 取值失败的默认值 71 | * @param {Object} payload user {Boolean} 是否区分用户 72 | */ 73 | export function dbGet ({ 74 | dbName = 'database', 75 | path = '', 76 | defaultValue = '', 77 | user = false 78 | }) { 79 | return cloneDeep(db.get(pathInit({ 80 | dbName, 81 | path, 82 | user, 83 | defaultValue 84 | })).value()) 85 | } 86 | 87 | /** 88 | * @description 获取存储数据库对象 89 | * @param {Object} payload user {Boolean} 是否区分用户 90 | */ 91 | export function database ({ 92 | dbName = 'database', 93 | path = '', 94 | user = false, 95 | validator = () => true, 96 | defaultValue = '' 97 | } = {}) { 98 | return db.get(pathInit({ 99 | dbName, path, user, validator, defaultValue 100 | })) 101 | } 102 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.css) { %> 10 | 11 | 12 | <% } %> 13 | 14 | <% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %> 15 | 16 | <% } %> 17 | <%= VUE_APP_TITLE %> 18 | 26 | 31 | 32 | 33 | 34 | 39 |
40 |
41 |
42 | loading 46 |
47 | 54 |
55 |
56 | 57 | <% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %> 58 | 59 | <% } %> 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/layout/header-aside/components/menu-side/index.js: -------------------------------------------------------------------------------- 1 | import { mapState } from 'vuex' 2 | import menuMixin from '../mixin/menu' 3 | import { elMenuItem, elSubmenu } from '../utils/menu' 4 | import BScroll from 'better-scroll' 5 | 6 | export default { 7 | name: 'd2-layout-header-aside-menu-side', 8 | mixins: [ 9 | menuMixin 10 | ], 11 | render (createElement) { 12 | return createElement('div', { attrs: { class: 'd2-layout-header-aside-menu-side' } }, [ 13 | createElement('el-menu', { 14 | props: { collapse: this.asideCollapse, uniqueOpened: true }, 15 | ref: 'menu', 16 | on: { select: this.handleMenuSelect } 17 | }, this.aside.map(menu => (menu.children === undefined ? elMenuItem : elSubmenu).call(this, createElement, menu))), 18 | ...this.aside.length === 0 && !this.asideCollapse ? [ 19 | createElement('div', { attrs: { class: 'd2-layout-header-aside-menu-empty', flex: 'dir:top main:center cross:center' } }, [ 20 | createElement('d2-icon', { props: { name: 'inbox' } }), 21 | createElement('span', {}, '没有侧栏菜单') 22 | ]) 23 | ] : [] 24 | ]) 25 | }, 26 | data () { 27 | return { 28 | BS: null 29 | } 30 | }, 31 | computed: { 32 | ...mapState('d2admin/menu', [ 33 | 'aside', 34 | 'asideCollapse' 35 | ]) 36 | }, 37 | watch: { 38 | // 折叠和展开菜单的时候销毁 better scroll 39 | asideCollapse (val) { 40 | this.scrollDestroy() 41 | setTimeout(() => { 42 | this.scrollInit() 43 | }, 500) 44 | }, 45 | // 侧边栏数据发生变化时重新计算侧边栏展开状态 46 | aside: 'initOpenedMenu', 47 | // 路由变化时控制侧边栏激活状态 48 | '$route.fullPath': { 49 | handler () { 50 | this.$nextTick(() => { 51 | if (this.$refs.menu) this.$refs.menu.updateActiveIndex(this.$route.fullPath) 52 | }) 53 | }, 54 | immediate: true 55 | } 56 | }, 57 | mounted () { 58 | this.scrollInit() 59 | }, 60 | beforeDestroy () { 61 | this.scrollDestroy() 62 | }, 63 | methods: { 64 | /** 65 | * @description 将之前保存的展开状态恢复到侧边栏上 66 | */ 67 | initOpenedMenu () { 68 | this.$nextTick(() => { 69 | if (this.$refs.menu) this.$refs.menu.initOpenedMenu() 70 | }) 71 | }, 72 | /** 73 | * @description 初始化 betterscroll 74 | */ 75 | scrollInit () { 76 | this.BS = new BScroll(this.$el, { 77 | mouseWheel: true, 78 | click: true 79 | // 如果你愿意可以打开显示滚动条 80 | // scrollbar: { 81 | // fade: true, 82 | // interactive: false 83 | // } 84 | }) 85 | }, 86 | /** 87 | * @description 销毁 betterscroll 88 | */ 89 | scrollDestroy () { 90 | // https://github.com/d2-projects/d2-admin/issues/75 91 | try { 92 | this.BS.destroy() 93 | } catch (e) { 94 | delete this.BS 95 | this.BS = null 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/mixins/crud-pagination.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data () { 3 | return { 4 | // 分页 5 | pagination: { 6 | current: 1, 7 | size: 10, 8 | total: 0 9 | } 10 | } 11 | }, 12 | computed: { 13 | // 为搜索参数提供数据 14 | paginationSearchData () { 15 | return { 16 | page: this.pagination.current, 17 | page_size: this.pagination.size 18 | } 19 | }, 20 | // vNode 21 | // 小型分页 22 | vNodePaginationMini () { 23 | const node = 24 | { 28 | this.pagination.current = current 29 | this.research() 30 | } 31 | } 32 | current-page={ this.pagination.current } 33 | page-size={ this.pagination.size } 34 | total={ this.pagination.total } 35 | pager-count={ 5 } 36 | small> 37 | 38 | return node 39 | }, 40 | // vNode 41 | // 完整功能的分页 42 | vNodePaginationFull () { 43 | const node = 44 | { 49 | this.pagination.size = size 50 | this.research() 51 | } 52 | } 53 | on-current-change={ 54 | current => { 55 | this.pagination.current = current 56 | this.research() 57 | } 58 | } 59 | current-page={ this.pagination.current } 60 | page-size={ this.pagination.size } 61 | total={ this.pagination.total }> 62 | 63 | return node 64 | } 65 | }, 66 | methods: { 67 | /** 68 | * @description 分页组件 69 | * @description 整体更新 70 | * @description 这是一个常见的通用更新页码方式,适用于一般的查询返回 71 | */ 72 | paginationUpdate (page) { 73 | this.paginationUpdateCurrent(page.page_no) 74 | this.paginationUpdateSize(page.page_size) 75 | this.paginationUpdateTotal(page.tatal_count) 76 | }, 77 | /** 78 | * @description 重置分页 79 | */ 80 | paginationReset () { 81 | this.paginationUpdateCurrent(1) 82 | this.paginationUpdateSize(10) 83 | this.paginationUpdateTotal(0) 84 | }, 85 | /** 86 | * @description 分页组件 87 | * @description 更新当前页码 88 | */ 89 | paginationUpdateCurrent (value) { 90 | this.pagination.current = value 91 | }, 92 | /** 93 | * @description 分页组件 94 | * @description 更新分页大小 95 | */ 96 | paginationUpdateSize (value) { 97 | this.pagination.size = value 98 | }, 99 | /** 100 | * @description 分页组件 101 | * @description 更新总页数 102 | */ 103 | paginationUpdateTotal (value) { 104 | this.pagination.total = value 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/components/d2-avatar-uploader/component.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 51 | 52 | 118 | -------------------------------------------------------------------------------- /src/layout/header-aside/components/panel-search/components/panel-search-item/index.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 36 | 37 | 105 | -------------------------------------------------------------------------------- /src/api/service.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | import axios from 'axios' 3 | import { get } from 'lodash' 4 | import { Message, Notification } from 'element-ui' 5 | import utils from '@/utils' 6 | import env from '@/env' 7 | 8 | // 记录和显示错误 9 | function log (error) { 10 | store.dispatch('d2admin/log/push', { 11 | message: '数据请求异常', 12 | type: 'danger', 13 | meta: { 14 | error 15 | } 16 | }) 17 | if (env.NODE_ENV === 'development') { 18 | utils.log.danger('>>>>>> Error >>>>>>') 19 | console.error(error) 20 | } 21 | Message({ 22 | message: error.message, 23 | type: 'error', 24 | duration: 5 * 1000 25 | }) 26 | } 27 | 28 | const service = axios.create() 29 | 30 | //返回值code=1 31 | service.interceptors.response.use( 32 | async response => { 33 | const dataAxios = response.data 34 | if (dataAxios.code === 0) { 35 | // 正常返回数据 36 | return dataAxios.data 37 | } else { 38 | const error = new Error(dataAxios.msg) 39 | // 需要重新登录 40 | // 50008 - 无效的 token 41 | // 50012 - 其它客户端登录 42 | // 50014 - token 过期 43 | if ([ 44 | 50008, 45 | 50012, 46 | 50014 47 | ].indexOf(dataAxios.code) >= 0) { 48 | Notification.error({ 49 | title: dataAxios.msg, 50 | message: '请重新登录' 51 | }) 52 | await store.dispatch('d2admin/user/logout', { focus: true, remote: false, back: true }) 53 | } else { 54 | log(error) 55 | } 56 | return Promise.reject(error) 57 | } 58 | }, 59 | error => { 60 | if (error && error.response) { 61 | switch (error.response.status) { 62 | case 400: 63 | error.message = '请求错误' 64 | break 65 | case 401: 66 | error.message = '未授权,请登录' 67 | break 68 | case 403: 69 | error.message = '拒绝访问' 70 | break 71 | case 404: 72 | error.message = `请求地址出错: ${error.response.config.url}` 73 | break 74 | case 408: 75 | error.message = '请求超时' 76 | break 77 | case 500: 78 | error.message = '服务器内部错误' 79 | break 80 | case 501: 81 | error.message = '服务未实现' 82 | break 83 | case 502: 84 | error.message = '网关错误' 85 | break 86 | case 503: 87 | error.message = '服务不可用' 88 | break 89 | case 504: 90 | error.message = '网关超时' 91 | break 92 | case 505: 93 | error.message = 'HTTP版本不受支持' 94 | break 95 | default: 96 | break 97 | } 98 | } 99 | log(error) 100 | return Promise.reject(error) 101 | } 102 | ) 103 | 104 | export function request (config) { 105 | const token = utils.cookies.get('token') 106 | let configDefault = { 107 | headers: { 108 | 'Authorization': token, 109 | 'Content-Type': get(config, 'headers.Content-Type', 'application/json') 110 | }, 111 | timeout: 5000, 112 | baseURL: store.state.d2admin.api.base, 113 | data: {} 114 | } 115 | return service(Object.assign(configDefault, config)) 116 | } 117 | --------------------------------------------------------------------------------