Browse Source

框架升级 20220607

litong 2 years ago
parent
commit
1b4eebd7ba
35 changed files with 1793 additions and 1667 deletions
  1. 2 1
      admin/.env.dev
  2. 0 34
      admin/.gitignore
  3. 4 0
      admin/.npmrc
  4. 8 9
      admin/package.json
  5. 37 0
      admin/src/api/login.js
  6. 2 1
      admin/src/components/generator/config.js
  7. 42 0
      admin/src/components/parser/Parser.vue
  8. 3 3
      admin/src/layout/components/Sidebar/Logo.vue
  9. 32 53
      admin/src/layout/components/Sidebar/SidebarItem.vue
  10. 6 13
      admin/src/layout/components/TagsView/index.vue
  11. 7 30
      admin/src/main.js
  12. 6 1
      admin/src/router/index.js
  13. 0 1
      admin/src/store/index.js
  14. 3 0
      admin/src/store/modules/permission.js
  15. 26 2
      admin/src/store/modules/tagsView.js
  16. 81 7
      admin/src/utils/auth.js
  17. 2 2
      admin/src/utils/constants.js
  18. 12 0
      admin/src/utils/index.js
  19. 81 71
      admin/src/utils/request.js
  20. 2 2
      admin/src/views/bpm/model/index.vue
  21. 1 2
      admin/src/views/bpm/processInstance/create.vue
  22. 1 1
      admin/src/views/bpm/processInstance/index.vue
  23. 1 1
      admin/src/views/bpm/taskAssignRule/taskAssignRuleDialog.vue
  24. 3 3
      admin/src/views/index_old.vue
  25. 29 28
      admin/src/views/login.vue
  26. 8 8
      admin/src/views/pay/refund/index.vue
  27. 43 15
      admin/src/views/socialLogin.vue
  28. 236 0
      admin/src/views/sso.vue
  29. 12 14
      admin/src/views/system/oauth2/client/index.vue
  30. 112 304
      admin/src/views/system/role/index.vue
  31. 1 1
      admin/src/views/system/sensitiveWord/index.vue
  32. 1 1
      admin/src/views/system/sms/smsTemplate.vue
  33. 2 2
      admin/src/views/system/user/index.vue
  34. 12 12
      admin/vue.config.js
  35. 975 1045
      admin/yarn.lock

+ 2 - 1
admin/.env.dev

@@ -5,7 +5,8 @@ ENV = 'development'
 VUE_APP_TITLE = 铂原联动
 
 # 开发环境
-VUE_APP_BASE_API = '/prod-api'
+# VUE_APP_BASE_API = '/prod-api'
+VUE_APP_BASE_API = 'http://purchase.platomix.net/prod-api'
 
 # 路由懒加载
 VUE_CLI_BABEL_TRANSPILE_MODULES = true

+ 0 - 34
admin/.gitignore

@@ -1,4 +1,3 @@
-<<<<<<< HEAD
 .DS_Store
 node_modules/
 dist/
@@ -21,36 +20,3 @@ selenium-debug.log
 *.local
 
 package-lock.json
-=======
-/purchase_H5/.idea
-/purchase_H5/package-lock.json
-*.json
-/purchase_H5/node_modules
-/purchase_ao/package-lock.json
-/purchase_ao/node_modules
-/purchase_ao/.idea
-/newdemo
-
-**/unpackage
-**/node_modules
-**/.next
-**/.idea
-**/.vscode
-**/.temp
-**/dist
-
-**/.DS_Store
-**/.env.local
-**/.env.*.local
-**/npm-debug.log*
-**/yarn-debug.log*
-**/yarn-error.log*
-**/*.suo
-**/*.ntvs*
-**/*.njsproj
-**/*.sln
-**/*.sw?
-/purchase_H5/src/common/mixinTest.js
-/purchase_H5/src/views/testIndex.vue
-/purchase_H5/src/templates/temp-test
->>>>>>> 4d75eef8b8591cdb21dd50fe778626329898c497

+ 4 - 0
admin/.npmrc

@@ -0,0 +1,4 @@
+phantomjs_cdnurl=http://cnpmjs.org/downloads
+chromedriver_cdnurl=http://cdn.npm.taobao.org/dist/chromedriver
+sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
+registry=https://registry.npmmirror.com

+ 8 - 9
admin/package.json

@@ -12,7 +12,8 @@
     "build:dev": "vue-cli-service build --mode dev",
     "build:demo1024": "vue-cli-service build --mode demo1024",
     "preview": "node build/index.js --preview",
-    "lint": "eslint --ext .js,.vue src"
+    "lint": "eslint --ext .js,.vue src",
+    "clean": "rimraf node_modules"
   },
   "husky": {
     "hooks": {
@@ -39,10 +40,10 @@
     "url": "https://github.com/YunaiV/ruoyi-vue-pro"
   },
   "dependencies": {
-    "@babel/parser": "^7.7.4",
+    "@babel/parser": "7.7.4",
     "@riophae/vue-treeselect": "0.4.0",
     "axios": "0.24.0",
-    "bpmn-js-token-simulation": "^0.10.0",
+    "bpmn-js-token-simulation": "0.10.0",
     "clipboard": "2.0.8",
     "core-js": "^3.21.1",
     "echarts": "4.9.0",
@@ -53,13 +54,13 @@
     "js-beautify": "1.13.0",
     "js-cookie": "3.0.1",
     "jsencrypt": "3.0.0-rc.1",
-    "min-dash": "^3.5.2",
+    "min-dash": "3.5.2",
     "nprogress": "0.2.0",
     "qrcodejs2": "0.0.2",
-    "quill": "^1.3.7",
+    "quill": "1.3.7",
     "screenfull": "5.0.2",
     "sortablejs": "1.10.2",
-    "throttle-debounce": "^2.1.0",
+    "throttle-debounce": "2.1.0",
     "vue": "2.6.12",
     "vue-count-to": "1.0.13",
     "vue-cropper": "0.5.5",
@@ -68,10 +69,8 @@
     "vue-router": "3.4.9",
     "vuedraggable": "2.24.3",
     "vuex": "3.6.0",
-    "vxe": "0.0.1",
-    "vxe-table": "^3.5.6",
     "xe-utils": "^3.5.4",
-    "xml-js": "^1.6.11"
+    "xml-js": "1.6.11"
   },
   "devDependencies": {
     "@vue/cli-plugin-babel": "4.4.6",

+ 37 - 0
admin/src/api/login.js

@@ -109,3 +109,40 @@ export function refreshToken() {
     method: 'post'
   })
 }
+
+// ========== OAUTH 2.0 相关 ==========
+
+export function getAuthorize(clientId) {
+  return request({
+    url: '/system/oauth2/authorize?clientId=' + clientId,
+    method: 'get'
+  })
+}
+
+export function authorize(responseType, clientId, redirectUri, state,
+                          autoApprove, checkedScopes, uncheckedScopes) {
+  // 构建 scopes
+  const scopes = {};
+  for (const scope of checkedScopes) {
+    scopes[scope] = true;
+  }
+  for (const scope of uncheckedScopes) {
+    scopes[scope] = false;
+  }
+  // 发起请求
+  return service({
+    url: '/system/oauth2/authorize',
+    headers:{
+      'Content-type': 'application/x-www-form-urlencoded',
+    },
+    params: {
+      response_type: responseType,
+      client_id: clientId,
+      redirect_uri: redirectUri,
+      state: state,
+      auto_approve: autoApprove,
+      scope: JSON.stringify(scopes)
+    },
+    method: 'post'
+  })
+}

+ 2 - 1
admin/src/components/generator/config.js

@@ -499,7 +499,8 @@ export const selectComponents = [
     __slot__: {
       'list-type': true
     },
-    action: process.env.VUE_APP_BASE_API + "/admin-api/infra/file/upload", // 请求地址
+    // action: process.env.VUE_APP_BASE_API + "/admin-api/infra/file/upload", // 请求地址
+    action: '/infra/file/upload', // 请求地址
     disabled: false,
     accept: '',
     name: 'file',

+ 42 - 0
admin/src/components/parser/Parser.vue

@@ -1,6 +1,7 @@
 <script>
 import { deepClone } from '@/utils/index'
 import render from '@/components/render/render.js'
+import {getAccessToken} from "@/utils/auth";
 
 const ruleTrigger = {
   'el-input': 'blur',
@@ -79,10 +80,51 @@ function formBtns(h) {
 }
 
 function renderFormItem(h, elementList) {
+  const that = this
+  const data = this[this.formConf.formModel]
+  // const formRef = that.$refs[that.formConf.formRef] // 这里直接添加有问题,此时还找不到表单 $refs
   return elementList.map(scheme => {
     const config = scheme.__config__
     const layout = layouts[config.layout]
 
+    // edit by 芋道源码,解决 el-upload 上传的问题
+    // 参考 https://github.com/JakHuang/form-generator/blob/master/src/components/parser/example/Index.vue 实现
+    const vModel = scheme.__vModel__
+    const val = data[vModel]
+    if (scheme.__config__.tag === 'el-upload') {
+      // 回显图片
+      scheme['file-list'] = (val || []).map(url => ({ name: url, url }))
+      // 上传地址 + 请求头
+      scheme.action = process.env.VUE_APP_BASE_API + "/admin-api/infra/file/upload"
+      scheme.headers = { Authorization: "Bearer " + getAccessToken() }
+      // 注意 on-success 不能绑定箭头函数!!!
+      scheme['on-success'] = function (response, file, fileList) {
+        if (response.code !== 0) {
+          return;
+        }
+        // 添加到 data 中
+        const prev = data[vModel] || []
+        this.$set(data, vModel, [
+          ...prev,
+          response.data
+        ])
+        // 发起表单校验
+        that.$refs[that.formConf.formRef].validateField(vModel)
+      }
+      // 注意 on-remove 不能绑定箭头函数!!!
+      scheme['on-remove'] = function (file, fileList) {
+        // 移除从 data 中
+        const prev = data[vModel] || []
+        const index = prev.indexOf(file.response.data)
+        if (index === -1) {
+          return
+        }
+        prev.splice(index, 1) // 直接移除即可,无需重复 set,因为 array 是引用
+        // 发起表单校验
+        that.$refs[that.formConf.formRef].validateField(vModel)
+      }
+    }
+
     if (layout) {
       return layout.call(this, h, scheme)
     }

+ 3 - 3
admin/src/layout/components/Sidebar/Logo.vue

@@ -2,11 +2,11 @@
   <div class="sidebar-logo-container" :class="{'collapse':collapse}" :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }">
     <transition name="sidebarLogoFade">
       <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
-        <img v-if="logo" :src="logo" class="sidebar-logo" />
+<!--        <img v-if="logo" :src="logo" class="sidebar-logo" />-->
         <h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
       </router-link>
       <router-link v-else key="expand" class="sidebar-logo-link" to="/">
-        <img v-if="logo" :src="logo" class="sidebar-logo" />
+<!--        <img v-if="logo" :src="logo" class="sidebar-logo" />-->
         <h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">{{ title }} </h1>
       </router-link>
     </transition>
@@ -35,7 +35,7 @@ export default {
   },
   data() {
     return {
-      title: '铂原联动',
+      title: '管理系统',
       logo: logoImg
     }
   }

+ 32 - 53
admin/src/layout/components/Sidebar/SidebarItem.vue

@@ -1,37 +1,16 @@
 <template>
   <div v-if="!item.hidden">
-    <template
-      v-if="
-        hasOneShowingChild(item.children, item) &&
-        (!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
-        !item.alwaysShow
-      "
-    >
+    <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
       <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
-        <el-menu-item
-          :index="resolvePath(onlyOneChild.path)"
-          :class="{ 'submenu-title-noDropdown': !isNest }"
-        >
-          <item
-            :icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"
-            :title="onlyOneChild.meta.title"
-          />
+        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
+          <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
         </el-menu-item>
       </app-link>
     </template>
 
-    <el-submenu
-      v-else
-      ref="subMenu"
-      :index="resolvePath(item.path)"
-      popper-append-to-body
-    >
+    <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
       <template slot="title">
-        <item
-          v-if="item.meta"
-          :icon="item.meta && item.meta.icon"
-          :title="item.meta.title"
-        />
+        <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
       </template>
       <sidebar-item
         v-for="child in item.children"
@@ -46,72 +25,72 @@
 </template>
 
 <script>
-import path from "path";
-import { isExternal } from "@/utils/validate";
-import Item from "./Item";
-import AppLink from "./Link";
-import FixiOSBug from "./FixiOSBug";
+import path from 'path'
+import { isExternal } from '@/utils/validate'
+import Item from './Item'
+import AppLink from './Link'
+import FixiOSBug from './FixiOSBug'
 
 export default {
-  name: "SidebarItem",
+  name: 'SidebarItem',
   components: { Item, AppLink },
   mixins: [FixiOSBug],
   props: {
     // route object
     item: {
       type: Object,
-      required: true,
+      required: true
     },
     isNest: {
       type: Boolean,
-      default: false,
+      default: false
     },
     basePath: {
       type: String,
-      default: "",
-    },
+      default: ''
+    }
   },
   data() {
-    this.onlyOneChild = null;
-    return {};
+    this.onlyOneChild = null
+    return {}
   },
   methods: {
     hasOneShowingChild(children = [], parent) {
       if (!children) {
         children = [];
       }
-      const showingChildren = children.filter((item) => {
+      const showingChildren = children.filter(item => {
         if (item.hidden) {
-          return false;
+          return false
         } else {
           // Temp set(will be used if only has one showing child)
-          this.onlyOneChild = item;
-          return true;
+          this.onlyOneChild = item
+          return true
         }
-      });
+      })
 
       // When there is only one child router, the child router is displayed by default
       if (showingChildren.length === 1) {
-        return true;
+        return true
       }
 
       // Show parent if there are no child router to display
       if (showingChildren.length === 0) {
-        this.onlyOneChild = { ...parent, path: "", noShowingChildren: true };
-        return true;
+        this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
+        return true
       }
 
-      return false;
+      return false
     },
     resolvePath(routePath) {
       if (isExternal(routePath)) {
-        return routePath;
+        return routePath
       }
       if (isExternal(this.basePath)) {
-        return this.basePath;
+        return this.basePath
       }
-      return path.resolve(this.basePath, routePath);
-    },
-  },
-};
+      return path.resolve(this.basePath, routePath)
+    }
+  }
+}
 </script>

+ 6 - 13
admin/src/layout/components/TagsView/index.vue

@@ -152,31 +152,24 @@ export default {
       })
     },
     refreshSelectedTag(view) {
-      this.$store.dispatch('tagsView/delCachedView', view).then(() => {
-        const { fullPath } = view
-        this.$nextTick(() => {
-          this.$router.replace({
-            path: '/redirect' + fullPath
-          })
-        })
-      })
+      this.$tab.refreshPage(view);
     },
     closeSelectedTag(view) {
-      this.$store.dispatch('tagsView/delView', view).then(({ visitedViews }) => {
+      this.$tab.closePage(view).then(({ visitedViews }) => {
         if (this.isActive(view)) {
           this.toLastView(visitedViews, view)
         }
       })
     },
     closeRightTags() {
-      this.$store.dispatch('tagsView/delRightTags', this.selectedTag).then(visitedViews => {
+      this.$tab.closeRightPage(this.selectedTag).then(visitedViews => {
         if (!visitedViews.find(i => i.fullPath === this.$route.fullPath)) {
           this.toLastView(visitedViews)
         }
       })
     },
     closeLeftTags() {
-      this.$store.dispatch('tagsView/delLeftTags', this.selectedTag).then(visitedViews => {
+      this.$tab.closeLeftPage(this.selectedTag).then(visitedViews => {
         if (!visitedViews.find(i => i.fullPath === this.$route.fullPath)) {
           this.toLastView(visitedViews)
         }
@@ -184,12 +177,12 @@ export default {
     },
     closeOthersTags() {
       this.$router.push(this.selectedTag).catch(()=>{});
-      this.$store.dispatch('tagsView/delOthersViews', this.selectedTag).then(() => {
+      this.$tab.closeOtherPage(this.selectedTag).then(() => {
         this.moveToCurrentTag()
       })
     },
     closeAllTags(view) {
-      this.$store.dispatch('tagsView/delAllViews').then(({ visitedViews }) => {
+      this.$tab.closeAllPage().then(({ visitedViews }) => {
         if (this.affixTags.some(tag => tag.path === this.$route.path)) {
           return
         }

+ 7 - 30
admin/src/main.js

@@ -16,31 +16,16 @@ import plugins from './plugins' // plugins
 import './assets/icons' // icon
 import './permission' // permission control
 import './tongji' // 百度统计
-import {
-  getDicts
-} from "@/api/system/dict/data";
-import {
-  getConfigKey
-} from "@/api/infra/config";
-import {
-  parseTime,
-  resetForm,
-  addDateRange,
-  addBeginAndEndTime,
-  handleTree
-} from "@/utils/ruoyi";
+import { getDicts } from "@/api/system/dict/data";
+import { getConfigKey } from "@/api/infra/config";
+import { parseTime, resetForm, addDateRange, addBeginAndEndTime, handleTree} from "@/utils/ruoyi";
 import Pagination from "@/components/Pagination";
 // 自定义表格工具扩展
 import RightToolbar from "@/components/RightToolbar"
 // 代码高亮插件
 // import hljs from 'highlight.js'
 // import 'highlight.js/styles/github-gist.css'
-import {
-  DICT_TYPE,
-  getDictDataLabel,
-  getDictDatas,
-  getDictDatas2
-} from "@/utils/dict";
+import {DICT_TYPE, getDictDataLabel, getDictDatas, getDictDatas2} from "@/utils/dict";
 
 // 全局方法挂载
 Vue.prototype.getDicts = getDicts
@@ -84,8 +69,9 @@ import "bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css";
 import Tinymce from '@/components/tinymce/index.vue'
 Vue.component('tinymce', Tinymce)
 import '@/icons'
-import axios from 'axios'
-Vue.prototype.$axios = axios
+import request from "@/utils/request" // 实现 form generator 使用自己定义的 axios request 对象
+console.log(request)
+Vue.prototype.$axios = request
 import '@/styles/index.scss'
 
 /**
@@ -101,17 +87,8 @@ Vue.use(Element, {
   size: Cookies.get('size') || 'medium' // set element-ui default size
 })
 
-
-import 'xe-utils'
-import VXETable from 'vxe-table'
-import 'vxe-table/lib/style.css'
-Vue.use(VXETable)
-Vue.config.productionTip = false
-
 Vue.config.productionTip = false
 
-
-
 new Vue({
   el: '#app',
   router,

+ 6 - 1
admin/src/router/index.js

@@ -43,6 +43,11 @@ export const constantRoutes = [
     hidden: true
   },
   {
+    path: '/sso',
+    component: (resolve) => require(['@/views/sso'], resolve),
+    hidden: true
+  },
+  {
     path: '/social-login',
     component: (resolve) => require(['@/views/socialLogin'], resolve),
     hidden: true
@@ -172,7 +177,7 @@ Router.prototype.push = function push(location) {
 
 export default new Router({
   base: process.env.VUE_APP_APP_NAME ? process.env.VUE_APP_APP_NAME : "/",
-  mode: 'hash', // 去掉url中的#
+  mode: 'history', // 去掉url中的#
   scrollBehavior: () => ({y: 0}),
   routes: constantRoutes
 })

+ 0 - 1
admin/src/store/index.js

@@ -8,7 +8,6 @@ import settings from './modules/settings'
 import dict from './modules/dict'
 import getters from './getters'
 
-
 Vue.use(Vuex)
 
 const store = new Vuex.Store({

+ 3 - 0
admin/src/store/modules/permission.js

@@ -2,6 +2,7 @@ import { constantRoutes } from '@/router'
 import { getRouters } from '@/api/menu'
 import Layout from '@/layout/index'
 import ParentView from '@/components/ParentView';
+import { toCamelCase } from "@/utils";
 
 const permission = {
   state: {
@@ -56,6 +57,8 @@ function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
       icon: route.icon,
       noCache: !route.keepAlive,
     }
+    // 路由地址转首字母大写驼峰,作为路由名称,适配keepAlive
+    route.name = toCamelCase(route.path, true)
     route.hidden = !route.visible
     // 处理 component 属性
     if (route.children) { // 父节点

+ 26 - 2
admin/src/store/modules/tagsView.js

@@ -63,7 +63,7 @@ const mutations = {
       }
     }
   },
-
+  
   DEL_RIGHT_VIEWS: (state, view) => {
     const index = state.visitedViews.findIndex(v => v.path === view.path)
     if (index === -1) {
@@ -79,6 +79,23 @@ const mutations = {
       }
       return false
     })
+  },
+
+  DEL_LEFT_VIEWS: (state, view) => {
+    const index = state.visitedViews.findIndex(v => v.path === view.path)
+    if (index === -1) {
+      return
+    }
+    state.visitedViews = state.visitedViews.filter((item, idx) => {
+      if (idx >= index || (item.meta && item.meta.affix)) {
+        return true
+      }
+      const i = state.cachedViews.indexOf(item.name)
+      if (i > -1) {
+        state.cachedViews.splice(i, 1)
+      }
+      return false
+    })
   }
 }
 
@@ -172,7 +189,14 @@ const actions = {
       commit('DEL_RIGHT_VIEWS', view)
       resolve([...state.visitedViews])
     })
-  }
+  },
+
+  delLeftTags({ commit }, view) {
+    return new Promise(resolve => {
+      commit('DEL_LEFT_VIEWS', view)
+      resolve([...state.visitedViews])
+    })
+  },
 }
 
 export default {

+ 81 - 7
admin/src/utils/auth.js

@@ -1,22 +1,96 @@
-import Cookies from 'js-cookie'
+import {decrypt, encrypt} from "@/utils/jsencrypt";
 
 const AccessTokenKey = 'ACCESS_TOKEN'
 const RefreshTokenKey = 'REFRESH_TOKEN'
 
+// ========== Token 相关 ==========
+
 export function getAccessToken() {
-  return Cookies.get(AccessTokenKey)
+  return localStorage.getItem(AccessTokenKey)
 }
 
 export function getRefreshToken() {
-  return Cookies.get(RefreshTokenKey)
+  return localStorage.getItem(RefreshTokenKey)
 }
 
 export function setToken(token) {
-  Cookies.set(AccessTokenKey, token.accessToken)
-  Cookies.set(RefreshTokenKey, token.refreshToken)
+  localStorage.setItem(AccessTokenKey, token.accessToken)
+  localStorage.setItem(RefreshTokenKey, token.refreshToken)
 }
 
 export function removeToken() {
-  Cookies.remove(AccessTokenKey)
-  Cookies.remove(RefreshTokenKey)
+  localStorage.removeItem(AccessTokenKey)
+  localStorage.removeItem(RefreshTokenKey)
+}
+
+// ========== 账号相关 ==========
+
+const UsernameKey = 'USERNAME'
+const PasswordKey = 'PASSWORD'
+const RememberMeKey = 'REMEMBER_ME'
+
+export function getUsername() {
+  return localStorage.getItem(UsernameKey)
+}
+
+export function setUsername(username) {
+  localStorage.setItem(UsernameKey, username)
+}
+
+export function removeUsername() {
+  localStorage.removeItem(UsernameKey)
+}
+
+export function getPassword() {
+  const password = localStorage.getItem(PasswordKey)
+  return password ? decrypt(password) : undefined
+}
+
+export function setPassword(password) {
+  localStorage.setItem(PasswordKey, encrypt(password))
+}
+
+export function removePassword() {
+  localStorage.removeItem(PasswordKey)
+}
+
+export function getRememberMe() {
+  return localStorage.getItem(RememberMeKey) === 'true'
+}
+
+export function setRememberMe(rememberMe) {
+  localStorage.setItem(RememberMeKey, rememberMe)
+}
+
+export function removeRememberMe() {
+  localStorage.removeItem(RememberMeKey)
+}
+
+// ========== 租户相关 ==========
+
+const TenantIdKey = 'TENANT_ID'
+const TenantNameKey = 'TENANT_NAME'
+
+export function getTenantName() {
+  return localStorage.getItem(TenantNameKey)
+}
+
+export function setTenantName(username) {
+  localStorage.setItem(TenantNameKey, username)
+}
+
+export function removeTenantName() {
+  localStorage.removeItem(TenantNameKey)
+}
+
+export function getTenantId() {
+  return localStorage.getItem(TenantIdKey)
+}
+
+export function setTenantId(username) {
+  localStorage.setItem(TenantIdKey, username)
+}
+
+export function removeTenantId() {
+  localStorage.removeItem(TenantIdKey)
 }

+ 2 - 2
admin/src/utils/constants.js

@@ -75,13 +75,13 @@ export const SystemUserSocialTypeEnum = {
     title: "钉钉",
     type: 20,
     source: "dingtalk",
-    img: "https://cdn.jsdelivr.net/gh/justauth/justauth-oauth-logo@1.11/dingtalk.png",
+    img: "https://s1.ax1x.com/2022/05/22/OzMDRs.png",
   },
   WECHAT_ENTERPRISE: {
     title: "企业微信",
     type: 30,
     source: "wechat_enterprise",
-    img: "https://cdn.jsdelivr.net/gh/justauth/justauth-oauth-logo@1.11/wechat_enterprise.png",
+    img: "https://s1.ax1x.com/2022/05/22/OzMrzn.png",
   }
 }
 

+ 12 - 0
admin/src/utils/index.js

@@ -427,3 +427,15 @@ export function isNumberStr(str) {
   return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str)
 }
 
+// -转驼峰
+export function toCamelCase(str, upperCaseFirst) {
+  str = (str || '').toLowerCase().replace(/-(.)/g, function (match, group1) {
+    return group1.toUpperCase();
+  });
+
+  if (upperCaseFirst && str) {
+    str = str.charAt(0).toUpperCase() + str.slice(1);
+  }
+
+  return str;
+}

+ 81 - 71
admin/src/utils/request.js

@@ -1,12 +1,17 @@
 import axios from 'axios'
-import { Notification, MessageBox, Message } from 'element-ui'
+import {Message, MessageBox, Notification} from 'element-ui'
 import store from '@/store'
-import {getAccessToken, getRefreshToken, setToken} from '@/utils/auth'
+import {getAccessToken, getRefreshToken, getTenantId, setToken} from '@/utils/auth'
 import errorCode from '@/utils/errorCode'
-import Cookies from "js-cookie";
 import {getPath, getTenantEnable} from "@/utils/ruoyi";
 import {refreshToken} from "@/api/login";
 
+// 需要忽略的提示。忽略后,自动 Promise.reject('error')
+const ignoreMsgs = [
+  "无效的刷新令牌", // 刷新令牌被删除时,不用提示
+  "刷新令牌已过期" // 使用刷新令牌,刷新获取新的访问令牌时,结果因为过期失败,此时需要忽略。否则,会导致继续 401,无法跳转到登出界面
+]
+
 // 是否显示重新登录
 export let isRelogin = { show: false };
 // Axios 无感知刷新令牌,参考 https://www.dashingdog.cn/article/11 与 https://segmentfault.com/a/1190000020210980 实现
@@ -21,7 +26,9 @@ const service = axios.create({
   // axios中请求配置有baseURL选项,表示请求URL公共部分
   baseURL: process.env.VUE_APP_BASE_API + '/admin-api/', // 此处的 /admin-api/ 地址,原因是后端的基础路径为 /admin-api/
   // 超时
-  timeout: 30000
+  timeout: 30000,
+  // 禁用 Cookie 等信息
+  withCredentials: false,
 })
 // request拦截器
 service.interceptors.request.use(config => {
@@ -32,7 +39,7 @@ service.interceptors.request.use(config => {
   }
   // 设置租户
   if (getTenantEnable()) {
-    const tenantId = Cookies.get('tenantId');
+    const tenantId = getTenantId();
     if (tenantId) {
       config.headers['tenant-id'] = tenantId;
     }
@@ -66,81 +73,84 @@ service.interceptors.request.use(config => {
 })
 
 // 响应拦截器
-service.interceptors.response.use( async res => {
-    // 未设置状态码则默认成功状态
-    const code = res.data.code || 200;
-    // 获取错误信息
-    const msg = errorCode[code] || res.data.msg || errorCode['default']
-    if (code === 401) {
-      // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
-      if (!isRefreshToken) {
-        isRefreshToken = true;
-        // 1. 如果获取不到刷新令牌,则只能执行登出操作
-        if (!getRefreshToken()) {
-          return handleAuthorized();
-        }
-        // 2. 进行刷新访问令牌
-        try {
-          const refreshTokenRes = await refreshToken()
-          // 2.1 刷新成功,则回放队列的请求 + 当前请求
-          setToken(refreshTokenRes.data)
-          requestList.forEach(cb => cb())
-          return service(res.config)
-        } catch (e) {
-          // 2.2 刷新失败,则只能执行登出操作
-          // 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。
-          return handleAuthorized();
-        } finally {
-          requestList = []
-          isRefreshToken = false
-        }
-      } else {
-        // 添加到队列,等待刷新获取到新的令牌
-        return new Promise(resolve => {
-          requestList.push(() => {
-            config.headers['Authorization'] = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改
-            resolve(service(config))
-          })
-        })
+service.interceptors.response.use(async res => {
+  // 未设置状态码则默认成功状态
+  const code = res.data.code || 200;
+  // 获取错误信息
+  const msg = res.data.msg || errorCode[code] || errorCode['default']
+  if (ignoreMsgs.indexOf(msg) !== -1) { // 如果是忽略的错误码,直接返回 msg 异常
+    return Promise.reject(msg)
+  } else if (code === 401) {
+    // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
+    if (!isRefreshToken) {
+      isRefreshToken = true;
+      // 1. 如果获取不到刷新令牌,则只能执行登出操作
+      if (!getRefreshToken()) {
+        return handleAuthorized();
       }
-      return handleAuthorized();
-    } else if (code === 500) {
-      Message({
-        message: msg,
-        type: 'error'
-      })
-      return Promise.reject(new Error(msg))
-    } else if (code === 901) {
-      Message({
-        type: 'error',
-        duration: 0,
-        dangerouslyUseHTMLString: true,
-        message: '<div>演示模式,无法进行写操作</div>'
-          + '<div> &nbsp; </div>'
-          + '<div>参考 https://doc.iocoder.cn/ 教程</div>'
-          + '<div> &nbsp; </div>'
-          + '<div>5 分钟搭建本地环境</div>',
+      // 2. 进行刷新访问令牌
+      try {
+        const refreshTokenRes = await refreshToken()
+        // 2.1 刷新成功,则回放队列的请求 + 当前请求
+        setToken(refreshTokenRes.data)
+        requestList.forEach(cb => cb())
+        return service(res.config)
+      } catch (e) {// 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。
+        // 2.2 刷新失败,只回放队列的请求
+        requestList.forEach(cb => cb())
+        // 提示是否要登出。即不回放当前请求!不然会形成递归
+        return handleAuthorized();
+      } finally {
+        requestList = []
+        isRefreshToken = false
+      }
+    } else {
+      // 添加到队列,等待刷新获取到新的令牌
+      return new Promise(resolve => {
+        requestList.push(() => {
+          res.config.headers['Authorization'] = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改
+          resolve(service(res.config))
+        })
       })
-      return Promise.reject(new Error(msg))
-    } else if (code !== 200) {
+    }
+  } else if (code === 500) {
+    Message({
+      message: msg,
+      type: 'error'
+    })
+    return Promise.reject(new Error(msg))
+  } else if (code === 901) {
+    Message({
+      type: 'error',
+      duration: 0,
+      dangerouslyUseHTMLString: true,
+      message: '<div>演示模式,无法进行写操作</div>'
+        + '<div> &nbsp; </div>'
+        + '<div>参考 https://doc.iocoder.cn/ 教程</div>'
+        + '<div> &nbsp; </div>'
+        + '<div>5 分钟搭建本地环境</div>',
+    })
+    return Promise.reject(new Error(msg))
+  } else if (code !== 200) {
+    if (msg === '无效的刷新令牌') { // hard coding:忽略这个提示,直接登出
+      console.log(msg)
+    } else {
       Notification.error({
         title: msg
       })
-      return Promise.reject('error')
-    } else {
-      return res.data
     }
-  },
-  error => {
+    return Promise.reject('error')
+  } else {
+    return res.data
+  }
+}, error => {
     console.log('err' + error)
-    let { message } = error;
+    let {message} = error;
     if (message === "Network Error") {
       message = "后端接口连接异常";
-    }
-    else if (message.includes("timeout")) {
+    } else if (message.includes("timeout")) {
       message = "系统接口请求超时";
-    }
-    else if (message.includes("Request failed with status code")) {
+    } else if (message.includes("Request failed with status code")) {
       message = "系统接口" + message.substr(message.length - 3) + "异常";
     }
     Message({
@@ -155,7 +165,7 @@ service.interceptors.response.use( async res => {
 export function getBaseHeader() {
   return {
     'Authorization': "Bearer " + getAccessToken(),
-    'tenant-id': Cookies.get('tenantId'),
+    'tenant-id': getTenantId(),
   }
 }
 

+ 2 - 2
admin/src/views/bpm/model/index.vue

@@ -429,7 +429,7 @@ export default {
       this.$modal.confirm('是否删除该流程!!').then(function() {
         deleteModel(row.id).then(response => {
           that.getList();
-          that.msgSuccess("删除成功");
+          that.$modal.msgSuccess("删除成功");
         })
       }).catch(() => {});
     },
@@ -439,7 +439,7 @@ export default {
       this.$modal.confirm('是否部署该流程!!').then(function() {
         deployModel(row.id).then(response => {
           that.getList();
-          that.msgSuccess("部署成功");
+          that.$modal.msgSuccess("部署成功");
         })
       }).catch(() => {});
     },

+ 1 - 2
admin/src/views/bpm/processInstance/create.vue

@@ -55,10 +55,9 @@
 <script>
 import {getProcessDefinitionBpmnXML, getProcessDefinitionList} from "@/api/bpm/definition";
 import {DICT_TYPE, getDictDatas} from "@/utils/dict";
-import {getForm} from "@/api/bpm/form";
 import {decodeFields} from "@/utils/formGenerator";
 import Parser from '@/components/parser/Parser'
-import {createProcessInstance, getMyProcessInstancePage} from "@/api/bpm/processInstance";
+import {createProcessInstance} from "@/api/bpm/processInstance";
 
 // 流程实例的发起
 export default {

+ 1 - 1
admin/src/views/bpm/processInstance/index.vue

@@ -58,7 +58,7 @@
       </el-table-column>
       <el-table-column label="当前审批任务" align="center" prop="tasks">
         <template slot-scope="scope">
-          <el-button v-for="task in scope.row.tasks" type="text" @click="handleFormDetail(task.id)">
+          <el-button v-for="task in scope.row.tasks" :key="task.id" type="text" @click="handleFormDetail(task.id)">
             <span>{{ task.name }}</span>
           </el-button>
         </template>

+ 1 - 1
admin/src/views/bpm/taskAssignRule/taskAssignRuleDialog.vue

@@ -12,7 +12,7 @@
         </el-table-column>
         <el-table-column label="规则范围" align="center" prop="options" width="440px">
           <template slot-scope="scope">
-            <el-tag size="medium" v-if="scope.row.options" v-for="option in scope.row.options">
+            <el-tag size="medium" v-if="scope.row.options" :key="option" v-for="option in scope.row.options">
               {{ getAssignRuleOptionName(scope.row.type, option) }}
             </el-tag>
           </template>

File diff suppressed because it is too large
+ 3 - 3
admin/src/views/index_old.vue


+ 29 - 28
admin/src/views/login.vue

@@ -9,7 +9,7 @@
       <div class="field">
         <!-- [移动端]标题 -->
         <h2 class="mobile-title">
-          <h3 class="title">铂原联动后台管理系统</h3>
+          <h3 class="title">后台管理系统</h3>
         </h2>
 
         <!-- 表单 -->
@@ -107,9 +107,17 @@
 import {getCodeImg, sendSmsCode, socialAuthRedirect} from "@/api/login";
 import {getTenantIdByName} from "@/api/system/tenant";
 import Cookies from "js-cookie";
-import {decrypt, encrypt} from '@/utils/jsencrypt'
 import {SystemUserSocialTypeEnum} from "@/utils/constants";
 import {getTenantEnable} from "@/utils/ruoyi";
+import {
+  getPassword,
+  getRememberMe, getTenantName,
+  getUsername,
+  removePassword, removeRememberMe, removeTenantName,
+  removeUsername,
+  setPassword, setRememberMe, setTenantId, setTenantName,
+  setUsername
+} from "@/utils/auth";
 
 export default {
   name: "Login",
@@ -161,7 +169,7 @@ export default {
                 const tenantId = res.data;
                 if (tenantId && tenantId >= 0) {
                   // 设置租户
-                  Cookies.set("tenantId", tenantId);
+                  setTenantId(tenantId)
                   callback();
                 } else {
                   callback('租户不存在');
@@ -172,8 +180,6 @@ export default {
           }
         ]
       },
-
-
       loading: false,
       redirect: undefined,
       // 枚举
@@ -213,21 +219,16 @@ export default {
       });
     },
     getCookie() {
-      const username = Cookies.get("username");
-      const password = Cookies.get("password");
-      const rememberMe = Cookies.get('rememberMe')
-      const tenantName = Cookies.get('tenantName');
-      const mobile = Cookies.get('mobile');
-      const mobileCode = Cookies.get('mobileCode');
-      const loginType = Cookies.get('loginType');
+      const username = getUsername();
+      const password = getPassword();
+      const rememberMe = getRememberMe();
+      const tenantName = getTenantName();
       this.loginForm = {
-        username: username === undefined ? this.loginForm.username : username,
-        password: password === undefined ? this.loginForm.password : decrypt(password),
-        rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
-        tenantName: tenantName === undefined ? this.loginForm.tenantName : tenantName,
-        mobile: mobile === undefined ? this.loginForm.mobile : mobile,
-        mobileCode: mobileCode === undefined ? this.loginForm.mobileCode : mobileCode,
-        loginType: loginType === undefined ? this.loginForm.loginType : loginType,
+        ...this.loginForm,
+        username: username ? username : this.loginForm.username,
+        password: password ? password : this.loginForm.password,
+        rememberMe: rememberMe ? getRememberMe() : false,
+        tenantName: tenantName ? tenantName : this.loginForm.tenantName,
       };
     },
     handleLogin() {
@@ -236,18 +237,18 @@ export default {
           this.loading = true;
           // 设置 Cookie
           if (this.loginForm.rememberMe) {
-            Cookies.set("username", this.loginForm.username, {expires: 30});
-            Cookies.set("password", encrypt(this.loginForm.password), {expires: 30});
-            Cookies.set('rememberMe', this.loginForm.rememberMe, {expires: 30});
-            Cookies.set('tenantName', this.loginForm.tenantName, {expires: 30});
+            setUsername(this.loginForm.username)
+            setPassword(this.loginForm.password)
+            setRememberMe(this.loginForm.rememberMe)
+            setTenantName(this.loginForm.tenantName)
           } else {
-            Cookies.remove("username");
-            Cookies.remove("password");
-            Cookies.remove('rememberMe');
-            Cookies.remove('tenantName');
+            removeUsername()
+            removePassword()
+            removeRememberMe()
+            removeTenantName()
           }
           // 发起登陆
-          console.log("发起登录", this.loginForm);
+          // console.log("发起登录", this.loginForm);
           this.$store.dispatch(this.loginForm.loginType === "sms" ? "SmsLogin" : "Login", this.loginForm).then(() => {
             this.$router.push({path: this.redirect || "/"}).catch(() => {
             });

+ 8 - 8
admin/src/views/pay/refund/index.vue

@@ -74,7 +74,7 @@
       <!--      <el-table-column label="商户名称" align="center" prop="merchantName" width="120"/>-->
       <!--      <el-table-column label="应用名称" align="center" prop="appName" width="120"/>-->
       <el-table-column label="支付渠道" align="center" width="130">
-        <template v-slot="scope">
+        <template slot-scope="scope">
           <el-popover trigger="hover" placement="top">
             <p>商户名称: {{ scope.row.merchantName }}</p>
             <p>应用名称: {{ scope.row.appName }}</p>
@@ -88,7 +88,7 @@
       <!--      <el-table-column label="交易订单号" align="center" prop="tradeNo" width="140"/>-->
       <!--      <el-table-column label="商户订单编号" align="center" prop="merchantOrderId" width="140"/>-->
       <el-table-column label="商户订单号" align="left" width="230">
-        <template v-slot="scope">
+        <template slot-scope="scope">
           <p class="order-font">
             <el-tag size="mini">退款</el-tag>
             {{ scope.row.merchantRefundNo }}
@@ -100,7 +100,7 @@
         </template>
       </el-table-column>
       <el-table-column label="支付订单号" align="center" prop="merchantRefundNo" width="250">
-        <template v-slot="scope">
+        <template slot-scope="scope">
           <p class="order-font">
             <el-tag size="mini">交易</el-tag>
             {{ scope.row.tradeNo }}
@@ -112,7 +112,7 @@
         </template>
       </el-table-column>
       <el-table-column label="支付金额(元)" align="center" prop="payAmount" width="100">
-        <template v-slot="scope" class="">
+        <template slot-scope="scope" class="">
           ¥{{ parseFloat(scope.row.payAmount / 100).toFixed(2) }}
         </template>
       </el-table-column>
@@ -122,17 +122,17 @@
         </template>
       </el-table-column>
       <el-table-column label="退款类型" align="center" prop="type" width="80">
-        <template v-slot="scope">
+        <template slot-scope="scope">
           <dict-tag :type="DICT_TYPE.PAY_REFUND_ORDER_TYPE" :value="scope.row.type" />
         </template>
       </el-table-column>
       <el-table-column label="退款状态" align="center" prop="status">
-        <template v-slot="scope">
+        <template slot-scope="scope">
           <dict-tag :type="DICT_TYPE.PAY_REFUND_ORDER_STATUS" :value="scope.row.status" />
         </template>
       </el-table-column>
       <el-table-column label="回调状态" align="center" prop="notifyStatus">
-        <template v-slot="scope">
+        <template slot-scope="scope">
           <dict-tag :type="DICT_TYPE.PAY_ORDER_NOTIFY_STATUS" :value="scope.row.notifyStatus" />
         </template>
       </el-table-column>
@@ -183,7 +183,7 @@
           <el-tag class="tag-purple" size="mini">{{ parseFloat(refundDetail.refundAmount / 100).toFixed(2) }}</el-tag>
         </el-descriptions-item>
         <el-descriptions-item label="退款类型">
-          <template v-slot="scope">
+          <template slot-scope="scope">
             <dict-tag :type="DICT_TYPE.PAY_REFUND_ORDER_TYPE" :value="refundDetail.type" />
           </template>
         </el-descriptions-item>

+ 43 - 15
admin/src/views/socialLogin.vue

@@ -9,7 +9,7 @@
       <div class="field">
         <!-- [移动端]标题 -->
         <h2 class="mobile-title">
-          <h3 class="title">芋道后台管理系统</h3>
+          <h3 class="title">后台管理系统</h3>
         </h2>
 
         <!-- 表单 -->
@@ -19,7 +19,7 @@
             </el-tab-pane>
           </el-tabs>
           <div>
-            <el-form ref="loginForm" :model="loginForm" :rules="LoginRules" class="login-form">
+            <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
               <!-- 账号密码登录 -->
               <el-form-item prop="username">
                 <el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号">
@@ -65,16 +65,28 @@
 <script>
 import Cookies from "js-cookie";
 import { encrypt, decrypt } from '@/utils/jsencrypt'
+import {
+  getPassword, getRememberMe,
+  getUsername,
+  removePassword,
+  removeUsername,
+  setPassword,
+  setRememberMe,
+  setUsername
+} from "@/utils/auth";
+import {getCodeImg} from "@/api/login";
 
 export default {
   name: "ThirdLogin",
   data() {
     return {
+      codeUrl: "",
+      captchaEnable: true,
       loginForm: {
         loginType: "uname",
         username: "admin",
         password: "admin123",
-        rememberMe: false, // TODO 芋艿:后面看情况,去掉这块
+        rememberMe: false,
       },
       loginRules: {
         username: [
@@ -104,6 +116,7 @@ export default {
     this.getCookie();
     // 重定向地址
     this.redirect = this.$route.query.redirect;
+    this.getCode();
     // 社交登录相关
     this.type = this.$route.query.type;
     this.code = this.$route.query.code;
@@ -119,16 +132,30 @@ export default {
     });
   },
   methods: {
+    getCode() {
+      // 只有开启的状态,才加载验证码。默认开启
+      if (!this.captchaEnable) {
+        return;
+      }
+      // 请求远程,获得验证码
+      getCodeImg().then(res => {
+        res = res.data;
+        this.captchaEnable = res.enable;
+        if (this.captchaEnable) {
+          this.codeUrl = "data:image/gif;base64," + res.img;
+          this.loginForm.uuid = res.uuid;
+        }
+      });
+    },
     getCookie() {
-      const username = Cookies.get("username");
-      const password = Cookies.get("password");
-      const rememberMe = Cookies.get('rememberMe')
-      const loginType = Cookies.get('loginType');
+      const username = getUsername();
+      const password = getPassword();
+      const rememberMe = getRememberMe();
       this.loginForm = {
-        username: username === undefined ? this.loginForm.username : username,
-        password: password === undefined ? this.loginForm.password : decrypt(password),
-        rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
-        loginType: loginType === undefined ? this.loginForm.loginType : loginType,
+        username: username ? username : this.loginForm.username,
+        password: password ? password : this.loginForm.password,
+        rememberMe: rememberMe ? getRememberMe() : false,
+        loginType: this.loginForm.loginType,
       };
     },
     handleLogin() {
@@ -136,11 +163,12 @@ export default {
         if (valid) {
           this.loading = true;
           if (this.loginForm.rememberMe) {
-            Cookies.set("username", this.loginForm.username, { expires: 30 });
-            Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
+            setUsername(this.loginForm.username)
+            setPassword(this.loginForm.password)
+            setRememberMe(this.loginForm.rememberMe)
           } else {
-            Cookies.remove("username");
-            Cookies.remove("password");
+            removeUsername()
+            removePassword()
           }
           this.$store.dispatch("SocialLogin2", {
             code: this.code,

+ 236 - 0
admin/src/views/sso.vue

@@ -0,0 +1,236 @@
+<template>
+  <div class="container">
+    <div class="logo"></div>
+    <!-- 登录区域 -->
+    <div class="content">
+      <!-- 配图 -->
+      <div class="pic"></div>
+      <!-- 表单 -->
+      <div class="field">
+        <!-- [移动端]标题 -->
+        <h2 class="mobile-title">
+          <h3 class="title">后台管理系统</h3>
+        </h2>
+
+        <!-- 表单 -->
+        <div class="form-cont">
+          <el-tabs class="form" style=" float:none;" value="uname">
+            <el-tab-pane :label="'三方授权(' + client.name + ')'" name="uname">
+            </el-tab-pane>
+          </el-tabs>
+          <div>
+            <el-form ref="loginForm" :model="loginForm" :rules="LoginRules" class="login-form">
+              <el-form-item prop="tenantName" v-if="tenantEnable">
+                <el-input v-model="loginForm.tenantName" type="text" auto-complete="off" placeholder='租户'>
+                  <svg-icon slot="prefix" icon-class="tree" class="el-input__icon input-icon"/>
+                </el-input>
+              </el-form-item>
+              <!-- 授权范围的选择 -->
+              此第三方应用请求获得以下权限:
+              <el-form-item prop="scopes">
+                <el-checkbox-group v-model="loginForm.scopes">
+                  <el-checkbox v-for="scope in params.scopes" :label="scope" :key="scope"
+                               style="display: block; margin-bottom: -10px;">{{formatScope(scope)}}</el-checkbox>
+                </el-checkbox-group>
+              </el-form-item>
+              <!-- 下方的登录按钮 -->
+              <el-form-item style="width:100%;">
+                <el-button :loading="loading" size="medium" type="primary" style="width:60%;"
+                           @click.native.prevent="handleAuthorize(true)">
+                  <span v-if="!loading">同意授权</span>
+                  <span v-else>授 权 中...</span>
+                </el-button>
+                <el-button size="medium" style="width:36%"
+                           @click.native.prevent="handleAuthorize(false)">拒绝</el-button>
+              </el-form-item>
+            </el-form>
+          </div>
+        </div>
+      </div>
+    </div>
+    <!-- footer -->
+    <div class="footer">
+      Copyright © 2020-2022 iocoder.cn All Rights Reserved.
+    </div>
+  </div>
+</template>
+
+<script>
+import {getTenantIdByName} from "@/api/system/tenant";
+import {getTenantEnable} from "@/utils/ruoyi";
+import {authorize, getAuthorize} from "@/api/login";
+import {getTenantName, setTenantId} from "@/utils/auth";
+
+export default {
+  name: "Login",
+  data() {
+    return {
+      tenantEnable: true,
+      loginForm: {
+        tenantName: "铂原联动",
+        scopes: [], // 已选中的 scope 数组
+      },
+      params: { // URL 上的 client_id、scope 等参数
+        responseType: undefined,
+        clientId: undefined,
+        redirectUri: undefined,
+        state: undefined,
+        scopes: [], // 优先从 query 参数获取;如果未传递,从后端获取
+      },
+      client: { // 客户端信息
+        name: '',
+        logo: '',
+      },
+      LoginRules: {
+        tenantName: [
+          {required: true, trigger: "blur", message: "租户不能为空"},
+          {
+            validator: (rule, value, callback) => {
+              // debugger
+              getTenantIdByName(value).then(res => {
+                const tenantId = res.data;
+                if (tenantId && tenantId >= 0) {
+                  // 设置租户
+                  setTenantId(tenantId)
+                  callback();
+                } else {
+                  callback('租户不存在');
+                }
+              });
+            },
+            trigger: 'blur'
+          }
+        ]
+      },
+      loading: false
+    };
+  },
+  created() {
+    // 租户开关
+    this.tenantEnable = getTenantEnable();
+    this.getCookie();
+
+    // 解析参数
+    // 例如说【自动授权不通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read%20user.write
+    // 例如说【自动授权通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read
+    this.params.responseType = this.$route.query.response_type
+    this.params.clientId = this.$route.query.client_id
+    this.params.redirectUri = this.$route.query.redirect_uri
+    this.params.state = this.$route.query.state
+    if (this.$route.query.scope) {
+      this.params.scopes = this.$route.query.scope.split(' ')
+    }
+
+    // 如果有 scope 参数,先执行一次自动授权,看看是否之前都授权过了。
+    if (this.params.scopes.length > 0) {
+      this.doAuthorize(true, this.params.scopes, []).then(res => {
+        const href = res.data
+        if (!href) {
+          console.log('自动授权未通过!')
+          return;
+        }
+        location.href = href
+      })
+    }
+
+    // 获取授权页的基本信息
+    getAuthorize(this.params.clientId).then(res => {
+      this.client = res.data.client
+      // 解析 scope
+      let scopes
+      // 1.1 如果 params.scope 非空,则过滤下返回的 scopes
+      if (this.params.scopes.length > 0) {
+        scopes = []
+        for (const scope of res.data.scopes) {
+          if (this.params.scopes.indexOf(scope.key) >= 0) {
+            scopes.push(scope)
+          }
+        }
+      // 1.2 如果 params.scope 为空,则使用返回的 scopes 设置它
+      } else {
+        scopes = res.data.scopes
+        for (const scope of scopes) {
+          this.params.scopes.push(scope.key)
+        }
+      }
+      // 生成已选中的 checkedScopes
+      for (const scope of scopes) {
+        if (scope.value) {
+          this.loginForm.scopes.push(scope.key)
+        }
+      }
+    })
+  },
+  methods: {
+    getCookie() {
+      const tenantName = getTenantName();
+      this.loginForm = {
+        ...this.loginForm,
+        tenantName: tenantName ? tenantName : this.loginForm.tenantName,
+      };
+    },
+    handleAuthorize(approved) {
+      this.$refs.loginForm.validate(valid => {
+        if (!valid) {
+          return
+        }
+        this.loading = true
+        // 计算 checkedScopes + uncheckedScopes
+        let checkedScopes;
+        let uncheckedScopes;
+        if (approved) { // 同意授权,按照用户的选择
+          checkedScopes = this.loginForm.scopes
+          uncheckedScopes = this.params.scopes.filter(item => checkedScopes.indexOf(item) === -1)
+        } else { // 拒绝,则都是取消
+          checkedScopes = []
+          uncheckedScopes = this.params.scopes
+        }
+        // 提交授权的请求
+        this.doAuthorize(false, checkedScopes, uncheckedScopes).then(res => {
+          const href = res.data
+          if (!href) {
+            return;
+          }
+          location.href = href
+        }).finally(() => {
+          this.loading = false
+        })
+      })
+    },
+    doAuthorize(autoApprove, checkedScopes, uncheckedScopes) {
+      return authorize(this.params.responseType, this.params.clientId, this.params.redirectUri, this.params.state,
+          autoApprove, checkedScopes, uncheckedScopes)
+    },
+    formatScope(scope) {
+      // 格式化 scope 授权范围,方便用户理解。
+      // 这里仅仅是一个 demo,可以考虑录入到字典数据中,例如说字典类型 "system_oauth2_scope",它的每个 scope 都是一条字典数据。
+      switch (scope) {
+        case 'user.read': return '访问你的个人信息'
+        case 'user.write': return '修改你的个人信息'
+        default: return scope
+      }
+    }
+  }
+};
+</script>
+<style lang="scss" scoped>
+@import "~@/assets/styles/login.scss";
+.oauth-login {
+  display: flex;
+  align-items: cen;
+  cursor:pointer;
+}
+.oauth-login-item {
+  display: flex;
+  align-items: center;
+  margin-right: 10px;
+}
+.oauth-login-item img {
+  height: 25px;
+  width: 25px;
+}
+.oauth-login-item span:hover {
+  text-decoration: underline red;
+  color: red;
+}
+</style>

+ 12 - 14
admin/src/views/system/oauth2/client/index.vue

@@ -50,7 +50,7 @@
       </el-table-column>
       <el-table-column label="授权类型" align="center" prop="authorizedGrantTypes">
         <template slot-scope="scope">
-          <el-tag :disable-transitions="true" v-for="(authorizedGrantType, index) in scope.row.authorizedGrantTypes" :index="index">
+          <el-tag :disable-transitions="true" :key="index" v-for="(authorizedGrantType, index) in scope.row.authorizedGrantTypes" :index="index">
             {{ authorizedGrantType }}
           </el-tag>
         </template>
@@ -103,17 +103,6 @@
         <el-form-item label="刷新令牌的有效期" prop="refreshTokenValiditySeconds">
           <el-input-number v-model="form.refreshTokenValiditySeconds" placeholder="单位:秒" />
         </el-form-item>
-        <el-form-item label="可重定向的 URI 地址" prop="redirectUris">
-          <el-select v-model="form.redirectUris" multiple filterable allow-create placeholder="请输入可重定向的 URI 地址" style="width: 500px" >
-            <el-option v-for="redirectUri in form.redirectUris" :key="redirectUri" :label="redirectUri" :value="redirectUri"/>
-          </el-select>
-        </el-form-item>
-        <el-form-item label="是否自动授权" prop="autoApprove">
-          <el-radio-group v-model="form.autoApprove">
-            <el-radio :key="true" :label="true">自动登录</el-radio>
-            <el-radio :key="false" :label="false">手动登录</el-radio>
-          </el-radio-group>
-        </el-form-item>
         <el-form-item label="授权类型" prop="authorizedGrantTypes">
           <el-select v-model="form.authorizedGrantTypes" multiple filterable placeholder="请输入授权类型" style="width: 500px" >
             <el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYSTEM_OAUTH2_GRANT_TYPE)"
@@ -125,6 +114,16 @@
             <el-option v-for="scope in form.scopes" :key="scope" :label="scope" :value="scope"/>
           </el-select>
         </el-form-item>
+        <el-form-item label="自动授权范围" prop="autoApproveScopes">
+          <el-select v-model="form.autoApproveScopes" multiple filterable placeholder="请输入授权范围" style="width: 500px" >
+            <el-option v-for="scope in form.scopes" :key="scope" :label="scope" :value="scope"/>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="可重定向的 URI 地址" prop="redirectUris">
+          <el-select v-model="form.redirectUris" multiple filterable allow-create placeholder="请输入可重定向的 URI 地址" style="width: 500px" >
+            <el-option v-for="redirectUri in form.redirectUris" :key="redirectUri" :label="redirectUri" :value="redirectUri"/>
+          </el-select>
+        </el-form-item>
         <el-form-item label="权限" prop="authorities">
           <el-select v-model="form.authorities" multiple filterable allow-create placeholder="请输入权限" style="width: 500px" >
             <el-option v-for="authority in form.authorities" :key="authority" :label="authority" :value="authority"/>
@@ -196,7 +195,6 @@ export default {
         accessTokenValiditySeconds: [{ required: true, message: "访问令牌的有效期不能为空", trigger: "blur" }],
         refreshTokenValiditySeconds: [{ required: true, message: "刷新令牌的有效期不能为空", trigger: "blur" }],
         redirectUris: [{ required: true, message: "可重定向的 URI 地址不能为空", trigger: "blur" }],
-        autoApprove: [{ required: true, message: "是否自动授权不能为空", trigger: "blur" }],
         authorizedGrantTypes: [{ required: true, message: "授权类型不能为空", trigger: "blur" }],
       }
     };
@@ -235,9 +233,9 @@ export default {
         accessTokenValiditySeconds: 30 * 60,
         refreshTokenValiditySeconds: 30 * 24 * 60,
         redirectUris: [],
-        autoApprove: true,
         authorizedGrantTypes: [],
         scopes: [],
+        autoApproveScopes: [],
         authorities: [],
         resourceIds: [],
         additionalInformation: undefined,

+ 112 - 304
admin/src/views/system/role/index.vue

@@ -1,200 +1,79 @@
 <template>
   <div class="app-container">
-    <doc-alert
-      title="功能权限"
-      url="https://doc.iocoder.cn/resource-permission"
-    />
+    <doc-alert title="功能权限" url="https://doc.iocoder.cn/resource-permission" />
     <doc-alert title="数据权限" url="https://doc.iocoder.cn/data-permission" />
-
-    <el-form
-      :model="queryParams"
-      ref="queryForm"
-      v-show="showSearch"
-      :inline="true"
-    >
+    <el-form :model="queryParams" ref="queryForm" v-show="showSearch" :inline="true">
       <el-form-item label="角色名称" prop="name">
-        <el-input
-          v-model="queryParams.name"
-          placeholder="请输入角色名称"
-          clearable
-          size="small"
-          style="width: 240px"
-          @keyup.enter.native="handleQuery"
-        />
+        <el-input v-model="queryParams.name" placeholder="请输入角色名称" clearable size="small" style="width: 240px"
+                  @keyup.enter.native="handleQuery"/>
       </el-form-item>
       <el-form-item label="角色标识" prop="code">
-        <el-input
-          v-model="queryParams.code"
-          placeholder="请输入角色标识"
-          clearable
-          size="small"
-          style="width: 240px"
-          @keyup.enter.native="handleQuery"
-        />
+        <el-input v-model="queryParams.code" placeholder="请输入角色标识" clearable size="small" style="width: 240px"
+                  @keyup.enter.native="handleQuery"/>
       </el-form-item>
       <el-form-item label="状态" prop="status">
-        <el-select
-          v-model="queryParams.status"
-          placeholder="角色状态"
-          clearable
-          size="small"
-          style="width: 240px"
-        >
-          <el-option
-            v-for="dict in statusDictDatas"
-            :key="parseInt(dict.value)"
-            :label="dict.label"
-            :value="parseInt(dict.value)"
-          />
+        <el-select v-model="queryParams.status" placeholder="角色状态" clearable size="small" style="width: 240px">
+          <el-option v-for="dict in statusDictDatas" :key="parseInt(dict.value)" :label="dict.label" :value="parseInt(dict.value)"/>
         </el-select>
       </el-form-item>
       <el-form-item label="创建时间">
-        <el-date-picker
-          v-model="dateRange"
-          size="small"
-          style="width: 240px"
-          value-format="yyyy-MM-dd"
-          type="daterange"
-          range-separator="-"
-          start-placeholder="开始日期"
-          end-placeholder="结束日期"
-        ></el-date-picker>
+        <el-date-picker v-model="dateRange" size="small" style="width: 240px" value-format="yyyy-MM-dd" type="daterange"
+          range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker>
       </el-form-item>
       <el-form-item>
-        <el-button
-          type="primary"
-          icon="el-icon-search"
-          size="mini"
-          @click="handleQuery"
-          >搜索</el-button
-        >
-        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery"
-          >重置</el-button
-        >
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
       </el-form-item>
     </el-form>
 
     <el-row :gutter="10" class="mb8">
       <el-col :span="1.5">
-        <el-button
-          type="primary"
-          plain
-          icon="el-icon-plus"
-          size="mini"
-          @click="handleAdd"
-          v-hasPermi="['system:role:create']"
-          >新增</el-button
-        >
+        <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
+                   v-hasPermi="['system:role:create']">新增</el-button>
       </el-col>
       <el-col :span="1.5">
-        <el-button
-          type="warning"
-          icon="el-icon-download"
-          size="mini"
-          @click="handleExport"
-          :loading="exportLoading"
-          v-hasPermi="['system:role:export']"
-          >导出</el-button
-        >
+        <el-button type="warning" icon="el-icon-download" size="mini" @click="handleExport" :loading="exportLoading"
+                   v-hasPermi="['system:role:export']">导出</el-button>
       </el-col>
-      <right-toolbar
-        :showSearch.sync="showSearch"
-        @queryTable="getList"
-      ></right-toolbar>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
     <el-table v-loading="loading" :data="roleList">
       <el-table-column label="角色编号" prop="id" width="120" />
-      <el-table-column
-        label="角色名称"
-        prop="name"
-        :show-overflow-tooltip="true"
-        width="150"
-      />
-      <el-table-column
-        label="角色标识"
-        prop="code"
-        :show-overflow-tooltip="true"
-        width="150"
-      />
+      <el-table-column label="角色名称" prop="name" :show-overflow-tooltip="true" width="150" />
+      <el-table-column label="角色标识" prop="code" :show-overflow-tooltip="true" width="150" />
       <el-table-column label="角色类型" prop="type" width="80">
         <template slot-scope="scope">
-          <dict-tag
-            :type="DICT_TYPE.SYSTEM_ROLE_TYPE"
-            :value="scope.row.type"
-          />
+          <dict-tag :type="DICT_TYPE.SYSTEM_ROLE_TYPE" :value="scope.row.type"/>
         </template>
       </el-table-column>
       <el-table-column label="显示顺序" prop="sort" width="100" />
       <el-table-column label="状态" align="center" width="100">
         <template slot-scope="scope">
-          <el-switch
-            v-model="scope.row.status"
-            :active-value="0"
-            :inactive-value="1"
-            @change="handleStatusChange(scope.row)"
-          ></el-switch>
+          <el-switch v-model="scope.row.status" :active-value="0" :inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
         </template>
       </el-table-column>
-      <el-table-column
-        label="创建时间"
-        align="center"
-        prop="createTime"
-        width="180"
-      >
+      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
         <template slot-scope="scope">
           <span>{{ parseTime(scope.row.createTime) }}</span>
         </template>
       </el-table-column>
-      <el-table-column
-        label="操作"
-        align="center"
-        class-name="small-padding fixed-width"
-      >
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
         <template slot-scope="scope">
-          <el-button
-            size="mini"
-            type="text"
-            icon="el-icon-edit"
-            @click="handleUpdate(scope.row)"
-            v-hasPermi="['system:role:update']"
-            >修改</el-button
-          >
-          <el-button
-            size="mini"
-            type="text"
-            icon="el-icon-circle-check"
-            @click="handleMenu(scope.row)"
-            v-hasPermi="['system:permission:assign-role-menu']"
-            >菜单权限</el-button
-          >
-          <el-button
-            size="mini"
-            type="text"
-            icon="el-icon-circle-check"
-            @click="handleDataScope(scope.row)"
-            v-hasPermi="['system:permission:assign-role-data-scope']"
-            >数据权限</el-button
-          >
-          <el-button
-            size="mini"
-            type="text"
-            icon="el-icon-delete"
-            @click="handleDelete(scope.row)"
-            v-hasPermi="['system:role:delete']"
-            >删除</el-button
-          >
+          <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
+                     v-hasPermi="['system:role:update']">修改</el-button>
+          <el-button size="mini" type="text" icon="el-icon-circle-check" @click="handleMenu(scope.row)"
+                     v-hasPermi="['system:permission:assign-role-menu']">菜单权限</el-button>
+          <el-button size="mini" type="text" icon="el-icon-circle-check" @click="handleDataScope(scope.row)"
+                     v-hasPermi="['system:permission:assign-role-data-scope']">数据权限</el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
+                     v-hasPermi="['system:role:delete']">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
 
-    <pagination
-      v-show="total > 0"
-      :total="total"
-      :page.sync="queryParams.pageNo"
-      :limit.sync="queryParams.pageSize"
-      @pagination="getList"
-    />
+    <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
+                @pagination="getList"/>
 
     <!-- 添加或修改角色配置对话框 -->
     <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
@@ -206,18 +85,10 @@
           <el-input v-model="form.code" placeholder="请输入角色标识" />
         </el-form-item>
         <el-form-item label="角色顺序" prop="sort">
-          <el-input-number
-            v-model="form.sort"
-            controls-position="right"
-            :min="0"
-          />
+          <el-input-number v-model="form.sort" controls-position="right" :min="0" />
         </el-form-item>
         <el-form-item label="备注">
-          <el-input
-            v-model="form.remark"
-            type="textarea"
-            placeholder="请输入内容"
-          ></el-input>
+          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
         </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
@@ -227,12 +98,7 @@
     </el-dialog>
 
     <!-- 分配角色的数据权限对话框 -->
-    <el-dialog
-      title="分配数据权限"
-      :visible.sync="openDataScope"
-      width="500px"
-      append-to-body
-    >
+    <el-dialog title="分配数据权限" :visible.sync="openDataScope" width="500px" append-to-body>
       <el-form :model="form" label-width="80px">
         <el-form-item label="角色名称">
           <el-input v-model="form.name" :disabled="true" />
@@ -250,25 +116,10 @@
             ></el-option>
           </el-select>
         </el-form-item>
-        <el-form-item
-          label="数据权限"
-          v-show="form.dataScope === SysDataScopeEnum.DEPT_CUSTOM"
-        >
-          <el-checkbox
-            :checked="!form.deptCheckStrictly"
-            @change="handleCheckedTreeConnect($event, 'dept')"
-            >父子联动(选中父节点,自动选择子节点)</el-checkbox
-          >
-          <el-checkbox
-            v-model="deptExpand"
-            @change="handleCheckedTreeExpand($event, 'dept')"
-            >展开/折叠</el-checkbox
-          >
-          <el-checkbox
-            v-model="deptNodeAll"
-            @change="handleCheckedTreeNodeAll($event, 'dept')"
-            >全选/全不选</el-checkbox
-          >
+        <el-form-item label="数据权限" v-show="form.dataScope === SysDataScopeEnum.DEPT_CUSTOM">
+          <el-checkbox :checked="!form.deptCheckStrictly" @change="handleCheckedTreeConnect($event, 'dept')">父子联动(选中父节点,自动选择子节点)</el-checkbox>
+          <el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand($event, 'dept')">展开/折叠</el-checkbox>
+          <el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">全选/全不选</el-checkbox>
           <el-tree
             class="tree-border"
             :data="deptOptions"
@@ -289,12 +140,7 @@
     </el-dialog>
 
     <!-- 分配角色的菜单权限对话框 -->
-    <el-dialog
-      :title="title"
-      :visible.sync="openMenu"
-      width="500px"
-      append-to-body
-    >
+    <el-dialog :title="title" :visible.sync="openMenu" width="500px" append-to-body>
       <el-form :model="form" label-width="80px">
         <el-form-item label="角色名称">
           <el-input v-model="form.name" :disabled="true" />
@@ -303,26 +149,10 @@
           <el-input v-model="form.code" :disabled="true" />
         </el-form-item>
         <el-form-item label="菜单权限">
-          <el-checkbox
-            v-model="menuExpand"
-            @change="handleCheckedTreeExpand($event, 'menu')"
-            >展开/折叠</el-checkbox
-          >
-          <el-checkbox
-            v-model="menuNodeAll"
-            @change="handleCheckedTreeNodeAll($event, 'menu')"
-            >全选/全不选</el-checkbox
-          >
-          <el-tree
-            class="tree-border"
-            :data="menuOptions"
-            show-checkbox
-            ref="menu"
-            node-key="id"
-            :check-strictly="form.menuCheckStrictly"
-            empty-text="加载中,请稍后"
-            :props="defaultProps"
-          ></el-tree>
+          <el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">展开/折叠</el-checkbox>
+          <el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选</el-checkbox>
+          <el-tree class="tree-border" :data="menuOptions" show-checkbox ref="menu" node-key="id"
+              :check-strictly="form.menuCheckStrictly" empty-text="加载中,请稍后" :props="defaultProps"></el-tree>
         </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
@@ -342,17 +172,13 @@ import {
   exportRole,
   getRole,
   listRole,
-  updateRole,
+  updateRole
 } from "@/api/system/role";
-import { listSimpleMenus } from "@/api/system/menu";
-import {
-  assignRoleMenu,
-  listRoleMenus,
-  assignRoleDataScope,
-} from "@/api/system/permission";
-import { listSimpleDepts } from "@/api/system/dept";
-import { CommonStatusEnum, SystemDataScopeEnum } from "@/utils/constants";
-import { DICT_TYPE, getDictDatas } from "@/utils/dict";
+import {listSimpleMenus} from "@/api/system/menu";
+import {assignRoleMenu, listRoleMenus, assignRoleDataScope} from "@/api/system/permission";
+import {listSimpleDepts} from "@/api/system/dept";
+import {CommonStatusEnum, SystemDataScopeEnum} from "@/utils/constants";
+import {DICT_TYPE, getDictDatas} from "@/utils/dict";
 
 export default {
   name: "Role",
@@ -393,25 +219,25 @@ export default {
         pageSize: 10,
         name: undefined,
         code: undefined,
-        status: undefined,
+        status: undefined
       },
       // 表单参数
       form: {},
       defaultProps: {
         label: "name",
-        children: "children",
+        children: "children"
       },
       // 表单校验
       rules: {
         name: [
-          { required: true, message: "角色名称不能为空", trigger: "blur" },
+          { required: true, message: "角色名称不能为空", trigger: "blur" }
         ],
         code: [
-          { required: true, message: "角色标识不能为空", trigger: "blur" },
+          { required: true, message: "角色标识不能为空", trigger: "blur" }
         ],
         sort: [
-          { required: true, message: "角色顺序不能为空", trigger: "blur" },
-        ],
+          { required: true, message: "角色顺序不能为空", trigger: "blur" }
+        ]
       },
 
       // 枚举
@@ -420,7 +246,7 @@ export default {
       // 数据字典
       roleTypeDictDatas: getDictDatas(DICT_TYPE.SYSTEM_ROLE_TYPE),
       statusDictDatas: getDictDatas(DICT_TYPE.COMMON_STATUS),
-      dataScopeDictDatas: getDictDatas(DICT_TYPE.SYSTEM_DATA_SCOPE),
+      dataScopeDictDatas: getDictDatas(DICT_TYPE.SYSTEM_DATA_SCOPE)
     };
   },
   created() {
@@ -430,34 +256,28 @@ export default {
     /** 查询角色列表 */
     getList() {
       this.loading = true;
-      listRole(
-        this.addDateRange(this.queryParams, [
-          this.dateRange[0] ? this.dateRange[0] + " 00:00:00" : undefined,
-          this.dateRange[1] ? this.dateRange[1] + " 23:59:59" : undefined,
-        ])
-      ).then((response) => {
-        this.roleList = response.data.list;
-        this.total = response.data.total;
-        this.loading = false;
-      });
+      listRole(this.addDateRange(this.queryParams, [
+        this.dateRange[0] ? this.dateRange[0] + ' 00:00:00' : undefined,
+        this.dateRange[1] ? this.dateRange[1] + ' 23:59:59' : undefined,
+      ])).then(
+        response => {
+          this.roleList = response.data.list;
+          this.total = response.data.total;
+          this.loading = false;
+        }
+      );
     },
     // 角色状态修改
     handleStatusChange(row) {
       // 此时,row 已经变成目标状态了,所以可以直接提交请求和提示
       let text = row.status === CommonStatusEnum.ENABLE ? "启用" : "停用";
-      this.$modal
-        .confirm('确认要"' + text + '""' + row.name + '"角色吗?')
-        .then(function () {
+      this.$modal.confirm('确认要"' + text + '""' + row.name + '"角色吗?').then(function() {
           return changeRoleStatus(row.id, row.status);
-        })
-        .then(() => {
+        }).then(() => {
           this.$modal.msgSuccess(text + "成功");
-        })
-        .catch(function () {
+        }).catch(function() {
           // 异常时,需要将 row.status 状态重置回之前的
-          row.status =
-            row.status === CommonStatusEnum.ENABLE
-              ? CommonStatusEnum.DISABLE
+          row.status = row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE
               : CommonStatusEnum.ENABLE;
         });
     },
@@ -495,7 +315,7 @@ export default {
         dataScope: undefined,
         deptCheckStrictly: false,
         menuCheckStrictly: true,
-        remark: undefined,
+        remark: undefined
       };
       this.resetForm("form");
     },
@@ -512,12 +332,12 @@ export default {
     },
     // 树权限(展开/折叠)
     handleCheckedTreeExpand(value, type) {
-      if (type === "menu") {
+      if (type === 'menu') {
         let treeList = this.menuOptions;
         for (let i = 0; i < treeList.length; i++) {
           this.$refs.menu.store.nodesMap[treeList[i].id].expanded = value;
         }
-      } else if (type === "dept") {
+      } else if (type === 'dept') {
         let treeList = this.deptOptions;
         for (let i = 0; i < treeList.length; i++) {
           this.$refs.dept.store.nodesMap[treeList[i].id].expanded = value;
@@ -526,18 +346,18 @@ export default {
     },
     // 树权限(全选/全不选)
     handleCheckedTreeNodeAll(value, type) {
-      if (type === "menu") {
-        this.$refs.menu.setCheckedNodes(value ? this.menuOptions : []);
-      } else if (type === "dept") {
+      if (type === 'menu') {
+        this.$refs.menu.setCheckedNodes(value ? this.menuOptions: []);
+      } else if (type === 'dept') {
         // this.$refs.dept.setCheckedNodes(value ? this.deptOptions: []);
-        this.$refs.dept.setCheckedNodes(value ? this.depts : []);
+        this.$refs.dept.setCheckedNodes(value ? this.depts: []);
       }
     },
     // 树权限(父子联动)
     handleCheckedTreeConnect(value, type) {
-      if (type === "menu") {
+      if (type === 'menu') {
         this.form.menuCheckStrictly = value;
-      } else if (type === "dept") {
+      } else if (type === 'dept') {
         this.form.deptCheckStrictly = !value;
       }
     },
@@ -550,8 +370,8 @@ export default {
     /** 修改按钮操作 */
     handleUpdate(row) {
       this.reset();
-      const id = row.id;
-      getRole(id).then((response) => {
+      const id = row.id
+      getRole(id).then(response => {
         this.form = response.data;
         this.open = true;
         this.title = "修改角色";
@@ -560,7 +380,7 @@ export default {
     /** 分配菜单权限操作 */
     handleMenu(row) {
       this.reset();
-      const id = row.id;
+      const id = row.id
       // 处理了 form 的角色 name 和 code 的展示
       this.form.id = id;
       this.form.name = row.name;
@@ -568,20 +388,21 @@ export default {
       // 打开弹窗
       this.openMenu = true;
       // 获得菜单列表
-      listSimpleMenus().then((response) => {
+      listSimpleMenus().then(response => {
         // 处理 menuOptions 参数
         this.menuOptions = [];
         this.menuOptions.push(...this.handleTree(response.data, "id"));
         // 获取角色拥有的菜单权限
-        listRoleMenus(id).then((response) => {
+        listRoleMenus(id).then(response => {
           // 设置为严格,避免设置父节点自动选中子节点,解决半选中问题
-          this.form.menuCheckStrictly = true;
+          this.form.menuCheckStrictly = true
           // 设置选中
           this.$refs.menu.setCheckedKeys(response.data);
           // 设置为非严格,继续使用半选中
-          this.form.menuCheckStrictly = false;
-        });
+          this.form.menuCheckStrictly = false
+        })
       });
+
     },
     /** 分配数据权限操作 */
     handleDataScope(row) {
@@ -593,31 +414,31 @@ export default {
       // 打开弹窗
       this.openDataScope = true;
       // 获得部门列表
-      listSimpleDepts().then((response) => {
+      listSimpleDepts().then(response => {
         // 处理 deptOptions 参数
         this.deptOptions = [];
         this.deptOptions.push(...this.handleTree(response.data, "id"));
         this.depts = response.data;
         // this.deptIds = response.data.map(x => x.id);
         // 获得角色拥有的数据权限
-        getRole(row.id).then((response) => {
+        getRole(row.id).then(response => {
           this.form.dataScope = response.data.dataScope;
           this.$refs.dept.setCheckedKeys(response.data.dataScopeDeptIds, false);
         });
       });
     },
     /** 提交按钮 */
-    submitForm: function () {
-      this.$refs["form"].validate((valid) => {
+    submitForm: function() {
+      this.$refs["form"].validate(valid => {
         if (valid) {
           if (this.form.id !== undefined) {
-            updateRole(this.form).then((response) => {
+            updateRole(this.form).then(response => {
               this.$modal.msgSuccess("修改成功");
               this.open = false;
               this.getList();
             });
           } else {
-            addRole(this.form).then((response) => {
+            addRole(this.form).then(response => {
               this.$modal.msgSuccess("新增成功");
               this.open = false;
               this.getList();
@@ -627,16 +448,14 @@ export default {
       });
     },
     /** 提交按钮(数据权限) */
-    submitDataScope: function () {
+    submitDataScope: function() {
       if (this.form.id !== undefined) {
         assignRoleDataScope({
           roleId: this.form.id,
           dataScope: this.form.dataScope,
-          dataScopeDeptIds:
-            this.form.dataScope !== SystemDataScopeEnum.DEPT_CUSTOM
-              ? []
-              : this.$refs.dept.getCheckedKeys(),
-        }).then((response) => {
+          dataScopeDeptIds: this.form.dataScope !== SystemDataScopeEnum.DEPT_CUSTOM ? [] :
+              this.$refs.dept.getCheckedKeys()
+        }).then(response => {
           this.$modal.msgSuccess("修改成功");
           this.openDataScope = false;
           this.getList();
@@ -644,15 +463,12 @@ export default {
       }
     },
     /** 提交按钮(菜单权限) */
-    submitMenu: function () {
+    submitMenu: function() {
       if (this.form.id !== undefined) {
         assignRoleMenu({
           roleId: this.form.id,
-          menuIds: [
-            ...this.$refs.menu.getCheckedKeys(),
-            ...this.$refs.menu.getHalfCheckedKeys(),
-          ],
-        }).then((response) => {
+          menuIds: [...this.$refs.menu.getCheckedKeys(), ...this.$refs.menu.getHalfCheckedKeys()]
+        }).then(response => {
           this.$modal.msgSuccess("修改成功");
           this.openMenu = false;
           this.getList();
@@ -662,32 +478,24 @@ export default {
     /** 删除按钮操作 */
     handleDelete(row) {
       const ids = row.id || this.ids;
-      this.$modal
-        .confirm('是否确认删除角色编号为"' + ids + '"的数据项?')
-        .then(function () {
+      this.$modal.confirm('是否确认删除角色编号为"' + ids + '"的数据项?').then(function() {
           return delRole(ids);
-        })
-        .then(() => {
+        }).then(() => {
           this.getList();
           this.$modal.msgSuccess("删除成功");
-        })
-        .catch(() => {});
+      }).catch(() => {});
     },
     /** 导出按钮操作 */
     handleExport() {
       const queryParams = this.queryParams;
-      this.$modal
-        .confirm("是否确认导出所有角色数据项?")
-        .then(function () {
+      this.$modal.confirm('是否确认导出所有角色数据项?').then(function() {
           this.exportLoading = true;
           return exportRole(queryParams);
-        })
-        .then((response) => {
-          this.$download.excel(response, "角色数据.xls");
+        }).then(response => {
+          this.$download.excel(response, '角色数据.xls');
           this.exportLoading = false;
-        })
-        .catch(() => {});
-    },
-  },
+      }).catch(() => {});
+    }
+  }
 };
 </script>

+ 1 - 1
admin/src/views/system/sensitiveWord/index.vue

@@ -54,7 +54,7 @@
       <el-table-column label="描述" align="center" prop="description"/>
       <el-table-column label="标签" align="center" prop="tags">
         <template slot-scope="scope">
-          <el-tag :disable-transitions="true" v-for="(tag, index) in scope.row.tags" :index="index">
+          <el-tag :disable-transitions="true" :key="index" v-for="(tag, index) in scope.row.tags" :index="index">
             {{ tag }}
           </el-tag>
         </template>

+ 1 - 1
admin/src/views/system/sms/smsTemplate.vue

@@ -147,7 +147,7 @@
         <el-form-item label="手机号" prop="mobile">
           <el-input v-model="sendSmsForm.mobile" placeholder="请输入手机号" />
         </el-form-item>
-        <el-form-item v-for="param in sendSmsForm.params" :label="'参数 {' + param + '}'" :prop="'templateParams.' + param">
+        <el-form-item v-for="param in sendSmsForm.params" :key="param" :label="'参数 {' + param + '}'" :prop="'templateParams.' + param">
           <el-input v-model="sendSmsForm.templateParams[param]" :placeholder="'请输入 ' + param + ' 参数'" />
         </el-form-item>
       </el-form>

+ 2 - 2
admin/src/views/system/user/index.vue

@@ -111,7 +111,7 @@
           </el-col>
           <el-col :span="12">
             <el-form-item label="归属部门" prop="deptId">
-              <treeselect v-model="form.deptId" :options="deptOptions" :show-count="true"
+              <treeselect v-model="form.deptId" :options="deptOptions" :show-count="true" :clearable="false"
                           placeholder="请选择归属部门" :normalizer="normalizer"/>
             </el-form-item>
           </el-col>
@@ -304,7 +304,7 @@ export default {
         // 设置上传的请求头部
         headers: getBaseHeader(),
         // 上传的地址
-        url: process.env.VUE_APP_BASE_API + '/admin-api/' + "/system/user/import"
+        url: process.env.VUE_APP_BASE_API + '/admin-api/system/user/import'
       },
       // 查询参数
       queryParams: {

+ 12 - 12
admin/vue.config.js

@@ -19,7 +19,7 @@ module.exports = {
   // 部署生产环境和开发环境下的URL。
   // 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上
   // 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。
-  publicPath: process.env.PUBLIC_PATH ? process.env.PUBLIC_PATH : '/ao/',
+  publicPath: process.env.PUBLIC_PATH ? process.env.PUBLIC_PATH : '/',
   // 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist)
   outputDir: 'dist',
   // 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下)
@@ -32,11 +32,11 @@ module.exports = {
   devServer: {
     host: '0.0.0.0',
     port: port,
-    open: false,
+    open: true,
     proxy: {
       // detail: https://cli.vuejs.org/config/#devserver-proxy
       [process.env.VUE_APP_BASE_API]: {
-        target: `http://purchase.platomix.net/prod-api`,
+        target: `http://localhost:48080`,
         // target: `http://api-dashboard.yudao.iocoder.cn`,
         changeOrigin: true,
         pathRewrite: {
@@ -60,15 +60,15 @@ module.exports = {
         '@': resolve('src')
       }
     },
-    // plugins: [
-    //   // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件
-    //   new CompressionPlugin({
-    //     test: /\.(js|css|html)?$/i,     // 压缩文件格式
-    //     filename: '[path].gz[query]',   // 压缩后的文件名
-    //     algorithm: 'gzip',              // 使用gzip压缩
-    //     minRatio: 0.8                   // 压缩率小于1才会压缩
-    //   })
-    // ],
+    plugins: [
+      // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件
+      new CompressionPlugin({
+        test: /\.(js|css|html)?$/i,     // 压缩文件格式
+        filename: '[path].gz[query]',   // 压缩后的文件名
+        algorithm: 'gzip',              // 使用gzip压缩
+        minRatio: 0.8                   // 压缩率小于1才会压缩
+      })
+    ],
   },
   chainWebpack(config) {
     config.plugins.delete('preload') // TODO: need test

File diff suppressed because it is too large
+ 975 - 1045
admin/yarn.lock