兰宝车间质量管理系统-前端
!64 版本升级
* Merge branch 'dev' of gitee.com:JavaLionLi/plus-ui into ts
* 升级依赖
* !61 fix: 删除重复环境变量ElUploadInstance
* fix: 删除重复环境变量ElUploadInstance
已添加1个文件
已删除1个文件
已修改96个文件
6342 ■■■■ 文件已修改
.eslintignore 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
.eslintrc.js 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.prettierrc 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 63 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/App.vue 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/tool/gen/index.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Breadcrumb/index.vue 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/BuildCode/index.vue 73 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/BuildCode/render.vue 67 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/DictTag/index.vue 60 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Editor/index.vue 158 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/FileUpload/index.vue 240 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Hamburger/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/HeaderSearch/index.vue 96 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/IconSelect/index.vue 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ImagePreview/index.vue 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ImageUpload/index.vue 239 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/LangSelect/index.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Pagination/index.vue 94 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/RightToolbar/index.vue 50 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/RuoYiDoc/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/RuoYiGit/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/SizeSelect/index.vue 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/SvgIcon/index.vue 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/TopNav/index.vue 98 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/TreeSelect/index.vue 123 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/iFrame/index.vue 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/directive/permission/index.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/AppMain.vue 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/IframeToggle/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/InnerLink/index.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Navbar.vue 91 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Settings/index.vue 176 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/Link.vue 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/Logo.vue 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/SidebarItem.vue 122 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/index.vue 41 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/SocialCallback/index.vue 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/TagsView/ScrollPane.vue 114 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/TagsView/index.vue 343 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/TopBar/search.vue 187 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/notice/index.vue 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/index.vue 64 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/permission.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/tab.ts 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.ts 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/notice.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/permission.ts 65 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/settings.ts 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/tagsView.ts 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/element.d.ts 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/env.d.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/global.d.ts 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/router.d.ts 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/setting.d.ts 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/propTypes.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/request.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/websocket.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/demo/demo/index.vue 104 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/demo/tree/index.vue 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/error/401.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/error/404.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/index.vue 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login.vue 55 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/cache/index.vue 74 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/logininfor/index.vue 142 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/online/index.vue 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/operlog/index.vue 73 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/redirect/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/register.vue 65 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/client/index.vue 124 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/config/index.vue 94 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dept/index.vue 126 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dict/data.vue 119 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dict/index.vue 91 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/menu/index.vue 132 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/notice/index.vue 76 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/oss/config.vue 153 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/oss/index.vue 132 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/post/index.vue 82 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/authUser.vue 57 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/index.vue 252 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/selectUser.vue 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/tenant/index.vue 128 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/tenantPackage/index.vue 93 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/authRole.vue 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/index.vue 288 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/index.vue 50 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/resetPwd.vue 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/thirdParty.vue 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/userAvatar.vue 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/userInfo.vue 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/basicInfoForm.vue 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/editTable.vue 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/genInfoForm.vue 49 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/importTable.vue 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/index.vue 74 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
tsconfig.json 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.eslintignore
@@ -14,4 +14,4 @@
.eslintrc.js
prettier.config.js
src/assets
tailwind.config.js
tailwind.config.js
.eslintrc.js
@@ -1,28 +1,35 @@
module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true
    node: true,
    es6: true
  },
  parser: 'vue-eslint-parser',
  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-essential',
    'plugin:@typescript-eslint/recommended',
    'plugin:vue/vue3-recommended',
    './.eslintrc-auto-import.json',
    'plugin:prettier/recommended'
    'plugin:@typescript-eslint/recommended',
    "prettier",
    'plugin:prettier/recommended',
  ],
  parserOptions: {
    ecmaVersion: '2020',
    sourceType: 'module',
    project: "./tsconfig.*?.json",
    parser: '@typescript-eslint/parser'
  },
  plugins: ['vue', '@typescript-eslint'],
  plugins: ['vue', '@typescript-eslint', 'import', 'promise', 'node', 'prettier'],
  rules: {
    'vue/multi-word-component-names': 'off',
    '@typescript-eslint/no-empty-function': 'off',
    '@typescript-eslint/no-explicit-any': 'off',
    // vue
    'vue/multi-word-component-names': 'off',
    'vue/valid-define-props': 'off',
    'vue/no-v-model-argument': 'off',
    'prefer-rest-params': 'off',
    // prettier
    'prettier/prettier': 'error',
    '@typescript-eslint/ban-types': [
      'error',
      {
.prettierrc
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
{
  "printWidth": 150,
  "tabWidth": 2,
  "useTabs": false,
  "semi": true,
  "singleQuote": true,
  "quoteProps": "as-needed",
  "jsxSingleQuote": false,
  "bracketSameLine": false,
  "trailingComma": "none",
  "bracketSpacing": true,
  "embeddedLanguageFormatting": "auto",
  "arrowParens": "always",
  "requirePragma": false,
  "insertPragma": false,
  "proseWrap": "preserve",
  "htmlWhitespaceSensitivity": "css",
  "vueIndentScriptAndStyle": false,
  "endOfLine": "auto"
}
package.json
@@ -6,9 +6,10 @@
  "license": "MIT",
  "scripts": {
    "dev": "vite serve --mode development",
    "build:prod": "vite build --mode production &&vue-tsc --noEmit",
    "build:prod": "vite build --mode production",
    "build:dev": "vite build --mode development",
    "preview": "vite preview",
    "lint": "eslint src/**/*.{ts,js,vue} --fix",
    "lint:eslint": "eslint  --fix --ext .ts,.js,.vue ./src ",
    "prepare": "husky install",
    "prettier": "prettier --write ."
  },
@@ -19,28 +20,28 @@
  "dependencies": {
    "@element-plus/icons-vue": "2.1.0",
    "@vueup/vue-quill": "1.2.0",
    "@vueuse/core": "9.5.0",
    "@vueuse/core": "10.7.0",
    "animate.css": "4.1.1",
    "await-to-js": "^3.0.0",
    "axios": "^1.3.4",
    "crypto-js": "^4.1.1",
    "echarts": "5.4.0",
    "element-plus": "2.2.27",
    "element-plus": "2.4.3",
    "file-saver": "2.0.5",
    "fuse.js": "6.6.2",
    "js-cookie": "3.0.1",
    "jsencrypt": "3.3.1",
    "crypto-js": "^4.1.1",
    "nprogress": "0.2.0",
    "path-browserify": "1.0.1",
    "path-to-regexp": "6.2.0",
    "pinia": "2.0.22",
    "pinia": "2.1.7",
    "screenfull": "6.0.0",
    "vform3-builds": "3.0.8",
    "vue": "3.2.45",
    "vue": "3.3.11",
    "vue-cropper": "1.0.3",
    "vue-i18n": "9.2.2",
    "vue-router": "4.1.4",
    "vue-types": "^5.0.3"
    "vue-router": "4.2.5",
    "vue-types": "5.1.1"
  },
  "devDependencies": {
    "@iconify/json": "^2.2.40",
@@ -51,34 +52,38 @@
    "@types/node": "18.14.2",
    "@types/nprogress": "0.2.0",
    "@types/path-browserify": "^1.0.0",
    "@typescript-eslint/eslint-plugin": "5.56.0",
    "@typescript-eslint/parser": "5.56.0",
    "@unocss/preset-attributify": "^0.50.6",
    "@unocss/preset-icons": "^0.50.6",
    "@unocss/preset-uno": "^0.50.6",
    "@vitejs/plugin-vue": "4.0.0",
    "@typescript-eslint/eslint-plugin": "6.14.0",
    "@typescript-eslint/parser": "6.14.0",
    "@unocss/preset-attributify": "^0.58.0",
    "@unocss/preset-icons": "^0.58.0",
    "@unocss/preset-uno": "^0.58.0",
    "@vue/compiler-sfc": "3.2.45",
    "@vitejs/plugin-vue": "4.5.2",
    "autoprefixer": "10.4.14",
    "eslint": "8.36.0",
    "eslint-config-prettier": "8.8.0",
    "eslint-plugin-prettier": "4.2.1",
    "eslint-plugin-vue": "9.9.0",
    "eslint": "8.55.0",
    "eslint-config-prettier": "9.1.0",
    "eslint-define-config": "2.0.0",
    "eslint-plugin-prettier": "5.0.1",
    "eslint-plugin-promise": "6.1.1",
    "eslint-plugin-node": "11.1.0",
    "eslint-plugin-import": "2.29.0",
    "eslint-plugin-vue": "9.19.2",
    "fast-glob": "^3.2.11",
    "husky": "7.0.4",
    "postcss": "^8.4.21",
    "prettier": "2.8.6",
    "prettier": "3.1.1",
    "sass": "1.56.1",
    "typescript": "4.9.5",
    "unocss": "^0.50.6",
    "unplugin-auto-import": "0.13.0",
    "unplugin-icons": "0.15.1",
    "unplugin-vue-components": "0.23.0",
    "vite": "4.3.1",
    "typescript": "5.2.2",
    "unocss": "^0.58.0",
    "unplugin-auto-import": "0.17.2",
    "unplugin-icons": "0.18.1",
    "unplugin-vue-components": "0.26.0",
    "unplugin-vue-setup-extend-plus": "0.4.9",
    "vite-plugin-compression": "0.5.1",
    "vite-plugin-svg-icons": "2.0.1",
    "unplugin-vue-setup-extend-plus": "0.4.9",
    "vitest": "^0.29.7",
    "vue-eslint-parser": "9.1.0",
    "vue-tsc": "0.35.0"
    "vue-eslint-parser": "9.3.2",
    "vue-tsc": "0.35.0",
    "vite": "5.0.4"
  }
}
src/App.vue
@@ -5,8 +5,8 @@
</template>
<script setup lang="ts">
import useSettingsStore from '@/store/modules/settings'
import { handleThemeStyle } from '@/utils/theme'
import useSettingsStore from '@/store/modules/settings';
import { handleThemeStyle } from '@/utils/theme';
import useAppStore from '@/store/modules/app';
const appStore = useAppStore();
@@ -15,7 +15,7 @@
onMounted(() => {
  nextTick(() => {
    // åˆå§‹åŒ–主题样式
    handleThemeStyle(useSettingsStore().theme)
  })
})
    handleThemeStyle(useSettingsStore().theme);
  });
});
</script>
src/api/tool/gen/index.ts
@@ -28,7 +28,7 @@
};
// ä¿®æ”¹ä»£ç ç”Ÿæˆä¿¡æ¯
export const updateGenTable = (data: DbTableForm) => {
export const updateGenTable = (data: DbTableForm): AxiosPromise<GenTableVO> => {
  return request({
    url: '/tool/gen',
    method: 'put',
@@ -37,7 +37,7 @@
};
// å¯¼å…¥è¡¨
export const importTable = (data: { tables: string; dataName: string }) => {
export const importTable = (data: { tables: string; dataName: string }): AxiosPromise<GenTableVO> => {
  return request({
    url: '/tool/gen/importTable',
    method: 'post',
src/components/Breadcrumb/index.vue
@@ -2,8 +2,7 @@
  <el-breadcrumb class="app-breadcrumb" separator="/">
    <transition-group name="breadcrumb">
      <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
        <span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect">{{
          item.meta?.title }}</span>
        <span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect">{{ item.meta?.title }}</span>
        <a v-else @click.prevent="handleLink(item)">{{ item.meta?.title }}</a>
      </el-breadcrumb-item>
    </transition-group>
@@ -11,42 +10,42 @@
</template>
<script setup lang="ts">
import { RouteLocationMatched } from 'vue-router'
import { RouteLocationMatched } from 'vue-router';
const route = useRoute();
const router = useRouter();
const levelList = ref<RouteLocationMatched[]>([])
const levelList = ref<RouteLocationMatched[]>([]);
const getBreadcrumb = () => {
  // only show routes with meta.title
  let matched = route.matched.filter(item => item.meta && item.meta.title);
  const first = matched[0]
  let matched = route.matched.filter((item) => item.meta && item.meta.title);
  const first = matched[0];
  // åˆ¤æ–­æ˜¯å¦ä¸ºé¦–页
  if (!isDashboard(first)) {
    matched = ([{ path: '/index', meta: { title: '首页' } }] as any).concat(matched)
    matched = ([{ path: '/index', meta: { title: '首页' } }] as any).concat(matched);
  }
  levelList.value = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
}
  levelList.value = matched.filter((item) => item.meta && item.meta.title && item.meta.breadcrumb !== false);
};
const isDashboard = (route: RouteLocationMatched) => {
  const name = route && route.name as string
  const name = route && (route.name as string);
  if (!name) {
    return false
    return false;
  }
  return name.trim() === 'Index'
}
  return name.trim() === 'Index';
};
const handleLink = (item: RouteLocationMatched) => {
  const { redirect, path } = item
  redirect ? router.push(redirect as string) : router.push(path)
}
  const { redirect, path } = item;
  redirect ? router.push(redirect as string) : router.push(path);
};
watchEffect(() => {
  // if you go to the redirect page, do not update the breadcrumbs
  if (route.path.startsWith('/redirect/')) return
  getBreadcrumb()
})
  if (route.path.startsWith('/redirect/')) return;
  getBreadcrumb();
});
onMounted(() => {
  getBreadcrumb();
})
});
</script>
<style lang="scss" scoped>
src/components/BuildCode/index.vue
@@ -1,53 +1,50 @@
<!-- ä»£ç æž„建 -->
<script setup lang="ts">
const props = defineProps({
  showBtn: {
    type: Boolean,
    default: false
  },
  formJson: {
    type: Object,
    default: undefined
  }
})
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const buildRef = ref();
const emits = defineEmits(['reJson', 'saveDesign']);
//获取表单json
const getJson = () => {
  const formJson = JSON.stringify(buildRef.value.getFormJson())
  const fieldJson = JSON.stringify(buildRef.value.getFieldWidgets())
  let data = {
    formJson, fieldJson
  }
  emits("saveDesign", data)
}
onMounted(() => {
  if (props.formJson) {
    buildRef.value.setFormJson(props.formJson)
  }
})
</script>
<template>
  <!-- ä»£ç æž„建 -->
  <div>
    <v-form-designer
      class="build"
      ref="buildRef"
      class="build"
      :designer-config="{ importJsonButton: true, exportJsonButton: true, exportCodeButton: true, generateSFCButton: true, formTemplates: true }"
    >
      <template #customToolButtons v-if="showBtn">
      <template v-if="showBtn" #customToolButtons>
        <el-button link type="primary" icon="Select" @click="getJson">保存</el-button>
      </template>
    </v-form-designer>
  </div>
</template>
<script setup lang="ts">
interface Props {
  showBtn: boolean;
  formJson: any;
}
const props = withDefaults(defineProps<Props>(), {
  showBtn: true,
  formJson: ''
});
const buildRef = ref();
const emits = defineEmits(['reJson', 'saveDesign']);
//获取表单json
const getJson = () => {
  const formJson = JSON.stringify(buildRef.value.getFormJson());
  const fieldJson = JSON.stringify(buildRef.value.getFieldWidgets());
  let data = {
    formJson,
    fieldJson
  };
  emits('saveDesign', data);
};
onMounted(() => {
  if (props.formJson) {
    buildRef.value.setFormJson(props.formJson);
  }
});
</script>
<style lang="scss">
.build {
  margin: 0 !important;
src/components/BuildCode/render.vue
@@ -1,26 +1,28 @@
<template>
  <div class="">
    <v-form-render ref="vFormRef" :form-json="formJson" :form-data="formData" />
  </div>
</template>
<!-- åŠ¨æ€è¡¨å•æ¸²æŸ“ -->
<script setup name="Render">
<script setup name="Render" lang="ts">
interface Props {
  formJson: string | object;
  formData: string | object;
  isView: boolean;
}
const props = defineProps({
  formJson: {
    type: [String, Object],
    default: ""
  },
  formData: {
    type: [String, Object],
    default: ""
  },
  isView: {
    type: Boolean,
    default: false
  }
})
const props = withDefaults(defineProps<Props>(), {
  formJson: '',
  formData: '',
  isView: false
});
const vFormRef = ref(null)
const vFormRef = ref(null);
// èŽ·å–è¡¨å•æ•°æ®-异步
const getFormData = () => {
  return vFormRef.value.getFormData()
}
  return vFormRef.value.getFormData();
};
/**
 * è®¾ç½®è¡¨å•内容
@@ -28,35 +30,28 @@
 * formConfig:{ formTemplate:表单模板,formData:表单数据,hiddenField:需要隐藏的字段字符串集合,disabledField:需要禁用的自读字符串集合}
 */
const initForm = (formConf) => {
  const { formTemplate, formData, hiddenField, disabledField } = toRaw(formConf)
  const { formTemplate, formData, hiddenField, disabledField } = toRaw(formConf);
  if (formTemplate) {
    vFormRef.value.setFormJson(formTemplate)
    vFormRef.value.setFormJson(formTemplate);
    if (formData) {
      vFormRef.value.setFormData(formData)
      vFormRef.value.setFormData(formData);
    }
    if (disabledField && disabledField.length > 0) {
      setTimeout(() => {
        vFormRef.value.disableWidgets(disabledField)
      }, 200)
        vFormRef.value.disableWidgets(disabledField);
      }, 200);
    }
    if (hiddenField && hiddenField.length > 0) {
      setTimeout(() => {
        vFormRef.value.hideWidgets(hiddenField)
      }, 200)
        vFormRef.value.hideWidgets(hiddenField);
      }, 200);
    }
    if (props.isView) {
      console.log(props.isView)
      setTimeout(() => {
        vFormRef.value.disableForm()
      }, 100)
        vFormRef.value.disableForm();
      }, 100);
    }
  }
}
defineExpose({ getFormData, initForm })
};
defineExpose({ getFormData, initForm });
</script>
<template>
  <div class="">
    <v-form-render ref="vFormRef" :form-json="formJson" :form-data="formData" />
  </div>
</template>
src/components/DictTag/index.vue
@@ -8,13 +8,13 @@
        </span>
        <el-tag
          v-else
          :disable-transitions="true"
          :key="item.value + ''"
          :disable-transitions="true"
          :index="index"
          :type="(item.elTagType === 'primary' || item.elTagType === 'default')? '' : item.elTagType"
          :class="item.elTagClass"
        >
          {{ item.label + " " }}
          {{ item.label + ' ' }}
        </el-tag>
      </template>
    </template>
@@ -25,57 +25,53 @@
</template>
<script setup lang="ts">
import { propTypes } from '@/utils/propTypes';
const props = defineProps({
  // æ•°æ®
  options: {
    type: Array as PropType<DictDataOption[]>,
    default: null,
  },
  // å½“前的值
  value: [Number, String, Array] as PropType<number | string | Array<number | string>>,
  // å½“未找到匹配的数据时,显示value
  showValue: propTypes.bool.def(true),
  separator: propTypes.string.def(","),
interface Props {
  options: Array<DictDataOption>;
  value: number | string | Array<number | string>;
  showValue: boolean;
  separator: string;
}
const props = withDefaults(defineProps<Props>(), {
  showValue: true,
  separator: ','
});
const values = computed(() => {
  if (props.value === '' || props.value === null || typeof props.value === "undefined") return []
  return Array.isArray(props.value) ? props.value.map(item => '' + item) : String(props.value).split(props.separator);
  if (props.value === '' || props.value === null || typeof props.value === 'undefined') return [];
  return Array.isArray(props.value) ? props.value.map((item) => '' + item) : String(props.value).split(props.separator);
});
const unmatch = computed(() => {
  if (props.options?.length == 0 || props.value === '' || props.value === null || typeof props.value === "undefined") return false
  if (props.options?.length == 0 || props.value === '' || props.value === null || typeof props.value === 'undefined') return false;
  // ä¼ å…¥å€¼ä¸ºéžæ•°ç»„
  values.value.forEach(item => {
    if (!props.options.some(v => v.value === item)) {
      return true // å¦‚果有未匹配项,将标志设置为true
  values.value.forEach((item) => {
    if (!props.options.some((v) => v.value === item)) {
      return true; // å¦‚果有未匹配项,将标志设置为true
    }
  })
  return false // è¿”回标志的值
  });
  return false; // è¿”回标志的值
});
const unmatchArray = computed(() => {
// è®°å½•未匹配的项
  // è®°å½•未匹配的项
  const itemUnmatchArray: Array<string | number> = [];
  if (props.value !== '' && props.value !== null && typeof props.value !== "undefined") {
    values.value.forEach(item => {
      if (!props.options.some(v => v.value === item)) {
  if (props.value !== '' && props.value !== null && typeof props.value !== 'undefined') {
    values.value.forEach((item) => {
      if (!props.options.some((v) => v.value === item)) {
        itemUnmatchArray.push(item);
      }
    })
    });
  }
  // æ²¡æœ‰value不显示
  return handleArray(itemUnmatchArray);
});
const handleArray = (array: Array<string | number>) => {
  if (array.length === 0) return "";
  if (array.length === 0) return '';
  return array.reduce((pre, cur) => {
    return pre + " " + cur;
    return pre + ' ' + cur;
  });
}
};
</script>
<style scoped>
src/components/Editor/index.vue
@@ -1,6 +1,8 @@
<template>
  <div>
    <el-upload
      v-if="type === 'url'"
      ref="uploadRef"
      :action="upload.url"
      :before-upload="handleBeforeUpload"
      :on-success="handleUploadSuccess"
@@ -9,18 +11,16 @@
      name="file"
      :show-file-list="false"
      :headers="upload.headers"
      ref="uploadRef"
      v-if="type === 'url'"
    >
    </el-upload>
    <div class="editor">
      <quill-editor
        ref="quillEditorRef"
        v-model:content="content"
        contentType="html"
        @textChange="(e: any) => $emit('update:modelValue', content)"
        content-type="html"
        :options="options"
        :style="styles"
        @text-change="(e: any) => $emit('update:modelValue', content)"
      />
    </div>
  </div>
@@ -30,7 +30,7 @@
import { QuillEditor, Quill } from '@vueup/vue-quill';
import '@vueup/vue-quill/dist/vue-quill.snow.css';
import { propTypes } from '@/utils/propTypes';
import { globalHeaders } from "@/utils/request";
import { globalHeaders } from '@/utils/request';
const props = defineProps({
  /* ç¼–辑器的内容 */
@@ -52,42 +52,42 @@
const upload = reactive<UploadOption>({
  headers: globalHeaders(),
  url: import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload'
})
});
const quillEditorRef = ref();
const options = ref({
  theme: "snow",
  theme: 'snow',
  bounds: document.body,
  debug: "warn",
  debug: 'warn',
  modules: {
    // å·¥å…·æ é…ç½®
    toolbar: {
      container: [
        ["bold", "italic", "underline", "strike"],       // åŠ ç²— æ–œä½“ ä¸‹åˆ’线 åˆ é™¤çº¿
        ["blockquote", "code-block"],                    // å¼•用  ä»£ç å—
        [{ list: "ordered" }, { list: "bullet" }],       // æœ‰åºã€æ— åºåˆ—表
        [{ indent: "-1" }, { indent: "+1" }],            // ç¼©è¿›
        [{ size: ["small", false, "large", "huge"] }],   // å­—体大小
        [{ header: [1, 2, 3, 4, 5, 6, false] }],         // æ ‡é¢˜
        [{ color: [] }, { background: [] }],             // å­—体颜色、字体背景颜色
        [{ align: [] }],                                 // å¯¹é½æ–¹å¼
        ["clean"],                                       // æ¸…除文本格式
        ["link", "image", "video"]                       // é“¾æŽ¥ã€å›¾ç‰‡ã€è§†é¢‘
        ['bold', 'italic', 'underline', 'strike'], // åŠ ç²— æ–œä½“ ä¸‹åˆ’线 åˆ é™¤çº¿
        ['blockquote', 'code-block'], // å¼•用  ä»£ç å—
        [{ list: 'ordered' }, { list: 'bullet' }], // æœ‰åºã€æ— åºåˆ—表
        [{ indent: '-1' }, { indent: '+1' }], // ç¼©è¿›
        [{ size: ['small', false, 'large', 'huge'] }], // å­—体大小
        [{ header: [1, 2, 3, 4, 5, 6, false] }], // æ ‡é¢˜
        [{ color: [] }, { background: [] }], // å­—体颜色、字体背景颜色
        [{ align: [] }], // å¯¹é½æ–¹å¼
        ['clean'], // æ¸…除文本格式
        ['link', 'image', 'video'] // é“¾æŽ¥ã€å›¾ç‰‡ã€è§†é¢‘
      ],
      handlers: {
        image: function (value: any) {
          if (value) {
            // è°ƒç”¨element图片上传
            (document.querySelector(".editor-img-uploader>.el-upload") as HTMLDivElement)?.click();
            (document.querySelector('.editor-img-uploader>.el-upload') as HTMLDivElement)?.click();
          } else {
            Quill.format("image", true);
            Quill.format('image', true);
          }
        },
      },
        }
      }
    }
  },
  placeholder: "请输入内容",
  readOnly: props.readOnly,
  placeholder: '请输入内容',
  readOnly: props.readOnly
});
const styles = computed(() => {
@@ -99,14 +99,18 @@
    style.height = `${props.height}px`;
  }
  return style;
})
});
const content = ref("");
watch(() => props.modelValue, (v) => {
  if (v !== content.value) {
    content.value = v === undefined ? "<p></p>" : v;
  }
}, { immediate: true });
const content = ref('');
watch(
  () => props.modelValue,
  (v) => {
    if (v !== content.value) {
      content.value = v === undefined ? '<p></p>' : v;
    }
  },
  { immediate: true }
);
// å›¾ç‰‡ä¸Šä¼ æˆåŠŸè¿”å›žå›¾ç‰‡åœ°å€
const handleUploadSuccess = (res: any) => {
@@ -117,7 +121,7 @@
    // èŽ·å–å…‰æ ‡ä½ç½®
    let length = quill.selection.savedRange.index;
    // æ’入图片,res为服务器返回的图片链接地址
    quill.insertEmbed(length, "image", res.data.url);
    quill.insertEmbed(length, 'image', res.data.url);
    // è°ƒæ•´å…‰æ ‡åˆ°æœ€åŽ
    quill.setSelection(length + 1);
    proxy?.$modal.closeLoading();
@@ -125,11 +129,11 @@
    proxy?.$modal.loading(res.msg);
    proxy?.$modal.closeLoading();
  }
}
};
// å›¾ç‰‡ä¸Šä¼ å‰æ‹¦æˆª
const handleBeforeUpload = (file: any) => {
  const type = ["image/jpeg", "image/jpg", "image/png", "image/svg"];
  const type = ['image/jpeg', 'image/jpg', 'image/png', 'image/svg'];
  const isJPG = type.includes(file.type);
  //检验文件格式
  if (!isJPG) {
@@ -146,13 +150,13 @@
  }
  proxy?.$modal.loading('正在上传文件,请稍候...');
  return true;
}
};
// å›¾ç‰‡å¤±è´¥æ‹¦æˆª
const handleUploadError = (err: any) => {
  console.error(err);
  proxy?.$modal.msgError('上传文件失败');
}
};
</script>
<style>
@@ -167,71 +171,71 @@
.quill-img {
  display: none;
}
.ql-snow .ql-tooltip[data-mode="link"]::before {
  content: "请输入链接地址:";
.ql-snow .ql-tooltip[data-mode='link']::before {
  content: '请输入链接地址:';
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
  border-right: 0;
  content: "保存";
  content: '保存';
  padding-right: 0;
}
.ql-snow .ql-tooltip[data-mode="video"]::before {
  content: "请输入视频地址:";
.ql-snow .ql-tooltip[data-mode='video']::before {
  content: '请输入视频地址:';
}
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
  content: "14px";
  content: '14px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
  content: "10px";
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='small']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before {
  content: '10px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
  content: "18px";
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='large']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before {
  content: '18px';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
  content: "32px";
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='huge']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before {
  content: '32px';
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
  content: "文本";
  content: '文本';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
  content: "标题1";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='1']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before {
  content: '标题1';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
  content: "标题2";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='2']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before {
  content: '标题2';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
  content: "标题3";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='3']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before {
  content: '标题3';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
  content: "标题4";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='4']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before {
  content: '标题4';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
  content: "标题5";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='5']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before {
  content: '标题5';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
  content: "标题6";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='6']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before {
  content: '标题6';
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
  content: "标准字体";
  content: '标准字体';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
  content: "衬线字体";
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='serif']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='serif']::before {
  content: '衬线字体';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
  content: "等宽字体";
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value='monospace']::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value='monospace']::before {
  content: '等宽字体';
}
</style>
src/components/FileUpload/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="upload-file">
    <el-upload
      ref="fileUploadRef"
      multiple
      :action="uploadFileUrl"
      :before-upload="handleBeforeUpload"
@@ -12,30 +13,29 @@
      :show-file-list="false"
      :headers="headers"
      class="upload-file-uploader"
      ref="fileUploadRef"
    >
      <!-- ä¸Šä¼ æŒ‰é’® -->
      <el-button type="primary">选取文件</el-button>
    </el-upload>
    <!-- ä¸Šä¼ æç¤º -->
    <div class="el-upload__tip" v-if="showTip">
    <div v-if="showTip" class="el-upload__tip">
      è¯·ä¸Šä¼ 
      <template v-if="fileSize">
        å¤§å°ä¸è¶…过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
      </template>
      <template v-if="fileType">
        æ ¼å¼ä¸º <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
        æ ¼å¼ä¸º <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
      </template>
      çš„æ–‡ä»¶
    </div>
    <!-- æ–‡ä»¶åˆ—表 -->
    <transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
      <li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList">
      <li v-for="(file, index) in fileList" :key="file.uid" class="el-upload-list__item ele-upload-list__item-content">
        <el-link :href="`${file.url}`" :underline="false" target="_blank">
          <span class="el-icon-document"> {{ getFileName(file.name) }} </span>
        </el-link>
        <div class="ele-upload-list__item-content-action">
          <el-link :underline="false" @click="handleDelete(index)" type="danger">删除</el-link>
          <el-link :underline="false" type="danger" @click="handleDelete(index)">删除</el-link>
        </div>
      </li>
    </transition-group>
@@ -43,20 +43,20 @@
</template>
<script setup lang="ts">
import { listByIds, delOss } from "@/api/system/oss";
import { listByIds, delOss } from '@/api/system/oss';
import { propTypes } from '@/utils/propTypes';
import { globalHeaders } from "@/utils/request";
import { globalHeaders } from '@/utils/request';
const props = defineProps({
    modelValue: [String, Object, Array],
    // æ•°é‡é™åˆ¶
    limit: propTypes.number.def(5),
    // å¤§å°é™åˆ¶(MB)
    fileSize: propTypes.number.def(5),
    // æ–‡ä»¶ç±»åž‹, ä¾‹å¦‚['png', 'jpg', 'jpeg']
    fileType: propTypes.array.def(["doc", "xls", "ppt", "txt", "pdf"]),
    // æ˜¯å¦æ˜¾ç¤ºæç¤º
    isShowTip: propTypes.bool.def(true),
  modelValue: [String, Object, Array],
  // æ•°é‡é™åˆ¶
  limit: propTypes.number.def(5),
  // å¤§å°é™åˆ¶(MB)
  fileSize: propTypes.number.def(5),
  // æ–‡ä»¶ç±»åž‹, ä¾‹å¦‚['png', 'jpg', 'jpeg']
  fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']),
  // æ˜¯å¦æ˜¾ç¤ºæç¤º
  isShowTip: propTypes.bool.def(true)
});
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -65,153 +65,163 @@
const uploadList = ref<any[]>([]);
const baseUrl = import.meta.env.VITE_APP_BASE_API;
const uploadFileUrl = ref(baseUrl + "/resource/oss/upload"); // ä¸Šä¼ æ–‡ä»¶æœåŠ¡å™¨åœ°å€
const uploadFileUrl = ref(baseUrl + '/resource/oss/upload'); // ä¸Šä¼ æ–‡ä»¶æœåŠ¡å™¨åœ°å€
const headers = ref(globalHeaders());
const fileList = ref<any[]>([]);
const showTip = computed(
    () => props.isShowTip && (props.fileType || props.fileSize)
);
const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize));
const fileUploadRef = ref<ElUploadInstance>();
watch(() => props.modelValue, async val => {
watch(
  () => props.modelValue,
  async (val) => {
    if (val) {
        let temp = 1;
        // é¦–先将值转为数组
        let list = [];
        if (Array.isArray(val)) {
            list = val;
        } else {
            const res = await listByIds(val as string)
            list = res.data.map((oss) => {
                const data = { name: oss.originalName, url: oss.url, ossId: oss.ossId };
                return data;
            });
        }
        // ç„¶åŽå°†æ•°ç»„转为对象数组
        fileList.value = list.map(item => {
            item = { name: item.name, url: item.url, ossId: item.ossId };
            item.uid = item.uid || new Date().getTime() + temp++;
            return item;
      let temp = 1;
      // é¦–先将值转为数组
      let list = [];
      if (Array.isArray(val)) {
        list = val;
      } else {
        const res = await listByIds(val as string);
        list = res.data.map((oss) => {
          const data = {
            name: oss.originalName,
            url: oss.url,
            ossId: oss.ossId
          };
          return data;
        });
      }
      // ç„¶åŽå°†æ•°ç»„转为对象数组
      fileList.value = list.map((item) => {
        item = { name: item.name, url: item.url, ossId: item.ossId };
        item.uid = item.uid || new Date().getTime() + temp++;
        return item;
      });
    } else {
        fileList.value = [];
        return [];
      fileList.value = [];
      return [];
    }
}, { deep: true, immediate: true });
  },
  { deep: true, immediate: true }
);
// ä¸Šä¼ å‰æ ¡æ£€æ ¼å¼å’Œå¤§å°
const handleBeforeUpload = (file: any) => {
    // æ ¡æ£€æ–‡ä»¶ç±»åž‹
    if (props.fileType.length) {
        const fileName = file.name.split('.');
        const fileExt = fileName[fileName.length - 1];
        const isTypeOk = props.fileType.indexOf(fileExt) >= 0;
        if (!isTypeOk) {
            proxy?.$modal.msgError(`文件格式不正确, è¯·ä¸Šä¼ ${props.fileType.join("/")}格式文件!`);
            return false;
        }
  // æ ¡æ£€æ–‡ä»¶ç±»åž‹
  if (props.fileType.length) {
    const fileName = file.name.split('.');
    const fileExt = fileName[fileName.length - 1];
    const isTypeOk = props.fileType.indexOf(fileExt) >= 0;
    if (!isTypeOk) {
      proxy?.$modal.msgError(`文件格式不正确, è¯·ä¸Šä¼ ${props.fileType.join('/')}格式文件!`);
      return false;
    }
    // æ ¡æ£€æ–‡ä»¶å¤§å°
    if (props.fileSize) {
        const isLt = file.size / 1024 / 1024 < props.fileSize;
        if (!isLt) {
            proxy?.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
            return false;
        }
  }
  // æ ¡æ£€æ–‡ä»¶å¤§å°
  if (props.fileSize) {
    const isLt = file.size / 1024 / 1024 < props.fileSize;
    if (!isLt) {
      proxy?.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
      return false;
    }
    proxy?.$modal.loading("正在上传文件,请稍候...");
    number.value++;
    return true;
}
  }
  proxy?.$modal.loading('正在上传文件,请稍候...');
  number.value++;
  return true;
};
// æ–‡ä»¶ä¸ªæ•°è¶…出
const handleExceed = () => {
    proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} ä¸ª!`);
}
  proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} ä¸ª!`);
};
// ä¸Šä¼ å¤±è´¥
const handleUploadError = () => {
    proxy?.$modal.msgError("上传文件失败");
}
  proxy?.$modal.msgError('上传文件失败');
};
// ä¸Šä¼ æˆåŠŸå›žè°ƒ
const handleUploadSuccess = (res: any, file: UploadFile) => {
    if (res.code === 200) {
        uploadList.value.push({ name: res.data.fileName, url: res.data.url, ossId: res.data.ossId });
        uploadedSuccessfully();
    } else {
        number.value--;
        proxy?.$modal.closeLoading();
        proxy?.$modal.msgError(res.msg);
        fileUploadRef.value?.handleRemove(file);
        uploadedSuccessfully();
    }
}
  if (res.code === 200) {
    uploadList.value.push({
      name: res.data.fileName,
      url: res.data.url,
      ossId: res.data.ossId
    });
    uploadedSuccessfully();
  } else {
    number.value--;
    proxy?.$modal.closeLoading();
    proxy?.$modal.msgError(res.msg);
    fileUploadRef.value?.handleRemove(file);
    uploadedSuccessfully();
  }
};
// åˆ é™¤æ–‡ä»¶
const handleDelete = (index: number) => {
    let ossId = fileList.value[index].ossId;
    delOss(ossId);
    fileList.value.splice(index, 1);
    emit("update:modelValue", listToString(fileList.value));
}
  let ossId = fileList.value[index].ossId;
  delOss(ossId);
  fileList.value.splice(index, 1);
  emit('update:modelValue', listToString(fileList.value));
};
// ä¸Šä¼ ç»“束处理
const uploadedSuccessfully = () => {
    if (number.value > 0 && uploadList.value.length === number.value) {
        fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
        uploadList.value = [];
        number.value = 0;
        emit("update:modelValue", listToString(fileList.value));
        proxy?.$modal.closeLoading();
    }
}
  if (number.value > 0 && uploadList.value.length === number.value) {
    fileList.value = fileList.value.filter((f) => f.url !== undefined).concat(uploadList.value);
    uploadList.value = [];
    number.value = 0;
    emit('update:modelValue', listToString(fileList.value));
    proxy?.$modal.closeLoading();
  }
};
// èŽ·å–æ–‡ä»¶åç§°
const getFileName = (name: string) => {
    // å¦‚果是url那么取最后的名字 å¦‚果不是直接返回
    if (name.lastIndexOf("/") > -1) {
        return name.slice(name.lastIndexOf("/") + 1);
    } else {
        return name;
    }
}
  // å¦‚果是url那么取最后的名字 å¦‚果不是直接返回
  if (name.lastIndexOf('/') > -1) {
    return name.slice(name.lastIndexOf('/') + 1);
  } else {
    return name;
  }
};
// å¯¹è±¡è½¬æˆæŒ‡å®šå­—符串分隔
const listToString = (list: any[], separator?: string) => {
    let strs = "";
    separator = separator || ",";
    list.forEach(item => {
        if (item.ossId) {
            strs += item.ossId + separator;
        }
    })
    return strs != "" ? strs.substring(0, strs.length - 1) : "";
}
  let strs = '';
  separator = separator || ',';
  list.forEach((item) => {
    if (item.ossId) {
      strs += item.ossId + separator;
    }
  });
  return strs != '' ? strs.substring(0, strs.length - 1) : '';
};
</script>
<style scoped lang="scss">
.upload-file-uploader {
    margin-bottom: 5px;
  margin-bottom: 5px;
}
.upload-file-list .el-upload-list__item {
    border: 1px solid #e4e7ed;
    line-height: 2;
    margin-bottom: 10px;
    position: relative;
  border: 1px solid #e4e7ed;
  line-height: 2;
  margin-bottom: 10px;
  position: relative;
}
.upload-file-list .ele-upload-list__item-content {
    display: flex;
    justify-content: space-between;
    align-items: center;
    color: inherit;
  display: flex;
  justify-content: space-between;
  align-items: center;
  color: inherit;
}
.ele-upload-list__item-content-action .el-link {
    margin-right: 10px;
  margin-right: 10px;
}
</style>
src/components/Hamburger/index.vue
@@ -1,5 +1,5 @@
<template>
  <div style="padding: 0 15px;" @click="toggleClick">
  <div style="padding: 0 15px" @click="toggleClick">
    <svg :class="{ 'is-active': isActive }" class="hamburger" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="64" height="64">
      <path
        d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"
@@ -13,12 +13,12 @@
defineProps({
  isActive: propTypes.bool.def(false)
})
});
const emit = defineEmits(['toggleClick'])
const emit = defineEmits(['toggleClick']);
const toggleClick = () => {
  emit('toggleClick');
}
};
</script>
<style scoped>
src/components/HeaderSearch/index.vue
@@ -1,6 +1,6 @@
<template>
  <div :class="{ 'show': show }" class="header-search">
    <svg-icon class-name="search-icon" icon-class="search" @click.stop="click"/>
  <div :class="{ show: show }" class="header-search">
    <svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
    <el-select
      ref="headerSearchSelectRef"
      v-model="search"
@@ -12,23 +12,22 @@
      class="header-search-select"
      @change="change"
    >
      <el-option v-for="option in options" :key="option.item.path" :value="option.item"
                 :label="option.item.title.join(' > ')"/>
      <el-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" />
    </el-select>
  </div>
</template>
<script setup lang="ts" name="HeaderSearch">
import Fuse from 'fuse.js';
import {getNormalPath} from '@/utils/ruoyi';
import {isHttp} from '@/utils/validate';
import { getNormalPath } from '@/utils/ruoyi';
import { isHttp } from '@/utils/validate';
import usePermissionStore from '@/store/modules/permission';
import {RouteOption} from 'vue-router';
import { RouteRecordRaw } from 'vue-router';
type Router = Array<{
  path: string;
  title: string[];
}>
}>;
const search = ref('');
const options = ref<any>([]);
@@ -40,36 +39,36 @@
const routes = computed(() => usePermissionStore().routes);
const click = () => {
  show.value = !show.value
  show.value = !show.value;
  if (show.value) {
    headerSearchSelectRef.value && headerSearchSelectRef.value.focus()
    headerSearchSelectRef.value && headerSearchSelectRef.value.focus();
  }
};
const close = () => {
  headerSearchSelectRef.value && headerSearchSelectRef.value.blur()
  options.value = []
  show.value = false
}
  headerSearchSelectRef.value && headerSearchSelectRef.value.blur();
  options.value = [];
  show.value = false;
};
const change = (val: any) => {
  const path = val.path;
  const query = val.query;
  if (isHttp(path)) {
    // http(s):// è·¯å¾„新窗口打开
    const pindex = path.indexOf("http");
    window.open(path.substr(pindex, path.length), "_blank");
    const pindex = path.indexOf('http');
    window.open(path.substr(pindex, path.length), '_blank');
  } else {
    if (query) {
      router.push({ path: path, query: JSON.parse(query) });
    } else {
      router.push(path)
      router.push(path);
    }
  }
  search.value = ''
  options.value = []
  search.value = '';
  options.value = [];
  nextTick(() => {
    show.value = false
  })
}
    show.value = false;
  });
};
const initFuse = (list: Router) => {
  fuse.value = new Fuse(list, {
    shouldSort: true,
@@ -77,20 +76,23 @@
    location: 0,
    distance: 100,
    minMatchCharLength: 1,
    keys: [{
      name: 'title',
      weight: 0.7
    }, {
      name: 'path',
      weight: 0.3
    }]
  })
}
    keys: [
      {
        name: 'title',
        weight: 0.7
      },
      {
        name: 'path',
        weight: 0.3
      }
    ]
  });
};
// Filter out the routes that can be displayed in the sidebar
// And generate the internationalized title
const generateRoutes = (routes: RouteOption[], basePath = '', prefixTitle: string[] = []) => {
  let res: Router = []
  routes.forEach(r => {
const generateRoutes = (routes: RouteRecordRaw[], basePath = '', prefixTitle: string[] = []) => {
  let res: Router = [];
  routes.forEach((r) => {
    // skip hidden router
    if (!r.hidden) {
      const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
@@ -98,7 +100,7 @@
        path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
        title: [...prefixTitle],
        query: ''
      }
      };
      if (r.meta && r.meta.title) {
        data.title = [...data.title, r.meta.title];
        if (r.redirect !== 'noRedirect') {
@@ -109,7 +111,7 @@
      }
      if (r.query) {
        data.query = r.query
        data.query = r.query;
      }
      // recursive child routes
@@ -120,20 +122,20 @@
        }
      }
    }
  })
  });
  return res;
}
};
const querySearch = (query: string) => {
  if (query !== '') {
    options.value = fuse.value.search(query)
    options.value = fuse.value.search(query);
  } else {
    options.value = []
    options.value = [];
  }
}
};
onMounted(() => {
  searchPool.value = generateRoutes(routes.value);
})
});
// watchEffect(() => {
//     searchPool.value = generateRoutes(routes.value)
@@ -141,15 +143,15 @@
watch(show, (value) => {
  if (value) {
    document.body.addEventListener('click', close)
    document.body.addEventListener('click', close);
  } else {
    document.body.removeEventListener('click', close)
    document.body.removeEventListener('click', close);
  }
})
});
watch(searchPool, (list) => {
  initFuse(list)
})
  initFuse(list);
});
</script>
<style lang="scss" scoped>
src/components/IconSelect/index.vue
@@ -1,6 +1,6 @@
<template>
  <div class="relative" :style="{ width: width }">
    <el-input v-model="modelValue" readonly @click="visible = !visible" placeholder="点击选择图标">
    <el-input v-model="modelValue" readonly placeholder="点击选择图标" @click="visible = !visible">
      <template #prepend>
        <svg-icon :icon-class="modelValue" />
      </template>
@@ -8,18 +8,18 @@
    <el-popover shadow="none" :visible="visible" placement="bottom-end" trigger="click" :width="450">
      <template #reference>
        <div @click="visible = !visible" class="cursor-pointer text-[#999] absolute right-[10px] top-0 height-[32px] leading-[32px]">
        <div class="cursor-pointer text-[#999] absolute right-[10px] top-0 height-[32px] leading-[32px]" @click="visible = !visible">
          <i-ep-caret-top v-show="visible"></i-ep-caret-top>
          <i-ep-caret-bottom v-show="!visible"></i-ep-caret-bottom>
        </div>
      </template>
      <el-input class="p-2" v-model="filterValue" placeholder="搜索图标" clearable @input="filterIcons" />
      <el-input v-model="filterValue" class="p-2" placeholder="搜索图标" clearable @input="filterIcons" />
      <el-scrollbar height="w-[200px]">
        <ul class="icon-list">
          <el-tooltip v-for="(iconName, index) in iconNames" :key="index" :content="iconName" placement="bottom" effect="light">
            <li :class="['icon-item', {active: modelValue == iconName}]" @click="selectedIcon(iconName)">
            <li :class="['icon-item', { active: modelValue == iconName }]" @click="selectedIcon(iconName)">
              <svg-icon color="var(--el-text-color-regular)" :icon-class="iconName" />
            </li>
          </el-tooltip>
@@ -50,13 +50,11 @@
 */
const filterIcons = () => {
  if (filterValue.value) {
    iconNames.value = icons.filter(iconName =>
      iconName.includes(filterValue.value)
    );
    iconNames.value = icons.filter((iconName) => iconName.includes(filterValue.value));
  } else {
    iconNames.value = icons;
  }
}
};
/**
 * é€‰æ‹©å›¾æ ‡
 * @param iconName é€‰æ‹©çš„图标名称
@@ -64,12 +62,12 @@
const selectedIcon = (iconName: string) => {
  emit('update:modelValue', iconName);
  visible.value = false;
}
};
</script>
<style scoped lang="scss">
.el-scrollbar {
  max-height: calc(50vh - 100px)!important;
  max-height: calc(50vh - 100px) !important;
  overflow-y: auto;
}
.el-divider--horizontal {
@@ -99,8 +97,8 @@
    }
  }
  .active {
      border-color: var(--el-color-primary);
      color: var(--el-color-primary);
    }
    border-color: var(--el-color-primary);
    color: var(--el-color-primary);
  }
}
</style>
src/components/ImagePreview/index.vue
@@ -15,11 +15,11 @@
  src: propTypes.string.def(''),
  width: {
    type: [Number, String],
    default: ""
    default: ''
  },
  height: {
    type: [Number, String],
    default: ""
    default: ''
  }
});
@@ -27,7 +27,7 @@
  if (!props.src) {
    return;
  }
  let real_src = props.src.split(",")[0];
  let real_src = props.src.split(',')[0];
  return real_src;
});
@@ -35,21 +35,17 @@
  if (!props.src) {
    return;
  }
  let real_src_list = props.src.split(",");
  let real_src_list = props.src.split(',');
  let srcList: string[] = [];
  real_src_list.forEach(item => {
  real_src_list.forEach((item) => {
    return srcList.push(item);
  });
  return srcList;
});
const realWidth = computed(() =>
  typeof props.width == "string" ? props.width : `${props.width}px`
);
const realWidth = computed(() => (typeof props.width == 'string' ? props.width : `${props.width}px`));
const realHeight = computed(() =>
  typeof props.height == "string" ? props.height : `${props.height}px`
);
const realHeight = computed(() => (typeof props.height == 'string' ? props.height : `${props.height}px`));
</script>
<style lang="scss" scoped>
src/components/ImageUpload/index.vue
@@ -1,6 +1,7 @@
<template>
  <div class="component-upload-image">
    <el-upload
      ref="imageUpload"
      multiple
      :action="uploadImgUrl"
      list-type="picture-card"
@@ -9,7 +10,6 @@
      :limit="limit"
      :on-error="handleUploadError"
      :on-exceed="handleExceed"
      ref="imageUpload"
      :before-remove="handleDelete"
      :show-file-list="true"
      :headers="headers"
@@ -22,13 +22,13 @@
      </el-icon>
    </el-upload>
    <!-- ä¸Šä¼ æç¤º -->
    <div class="el-upload__tip" v-if="showTip">
    <div v-if="showTip" class="el-upload__tip">
      è¯·ä¸Šä¼ 
      <template v-if="fileSize">
        å¤§å°ä¸è¶…过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
      </template>
      <template v-if="fileType">
        æ ¼å¼ä¸º <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
        æ ¼å¼ä¸º <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
      </template>
      çš„æ–‡ä»¶
    </div>
@@ -40,177 +40,176 @@
</template>
<script setup lang="ts">
import { listByIds, delOss } from "@/api/system/oss";
import { ComponentInternalInstance } from "vue";
import { OssVO } from "@/api/system/oss/types";
import { listByIds, delOss } from '@/api/system/oss';
import { OssVO } from '@/api/system/oss/types';
import { propTypes } from '@/utils/propTypes';
import {globalHeaders} from "@/utils/request";
import { globalHeaders } from '@/utils/request';
const props = defineProps({
    modelValue: [String, Object, Array],
    // å›¾ç‰‡æ•°é‡é™åˆ¶
    limit: propTypes.number.def(5),
    // å¤§å°é™åˆ¶(MB)
    fileSize: propTypes.number.def(5),
    // æ–‡ä»¶ç±»åž‹, ä¾‹å¦‚['png', 'jpg', 'jpeg']
    fileType: propTypes.array.def(["png", "jpg", "jpeg"]),
    // æ˜¯å¦æ˜¾ç¤ºæç¤º
    isShowTip: {
        type: Boolean,
        default: true
    },
  modelValue: [String, Object, Array],
  // å›¾ç‰‡æ•°é‡é™åˆ¶
  limit: propTypes.number.def(5),
  // å¤§å°é™åˆ¶(MB)
  fileSize: propTypes.number.def(5),
  // æ–‡ä»¶ç±»åž‹, ä¾‹å¦‚['png', 'jpg', 'jpeg']
  fileType: propTypes.array.def(['png', 'jpg', 'jpeg']),
  // æ˜¯å¦æ˜¾ç¤ºæç¤º
  isShowTip: {
    type: Boolean,
    default: true
  }
});
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const emit = defineEmits(['update:modelValue']);
const number = ref(0);
const uploadList = ref<any[]>([]);
const dialogImageUrl = ref("");
const dialogImageUrl = ref('');
const dialogVisible = ref(false);
const baseUrl = import.meta.env.VITE_APP_BASE_API;
const uploadImgUrl = ref(baseUrl + "/resource/oss/upload"); // ä¸Šä¼ çš„图片服务器地址
const uploadImgUrl = ref(baseUrl + '/resource/oss/upload'); // ä¸Šä¼ çš„图片服务器地址
const headers = ref(globalHeaders());
const fileList = ref<any[]>([]);
const showTip = computed(
    () => props.isShowTip && (props.fileType || props.fileSize)
);
const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize));
const imageUploadRef = ref<ElUploadInstance>();
watch(() => props.modelValue, async val => {
watch(
  () => props.modelValue,
  async (val) => {
    if (val) {
        // é¦–先将值转为数组
        let list: OssVO[] = [];
        if (Array.isArray(val)) {
            list = val as OssVO[];
      // é¦–先将值转为数组
      let list: OssVO[] = [];
      if (Array.isArray(val)) {
        list = val as OssVO[];
      } else {
        const res = await listByIds(val as string);
        list = res.data;
      }
      // ç„¶åŽå°†æ•°ç»„转为对象数组
      fileList.value = list.map((item) => {
        // å­—符串回显处理 å¦‚果此处存的是url可直接回显 å¦‚果存的是id需要调用接口查出来
        let itemData;
        if (typeof item === 'string') {
          itemData = { name: item, url: item };
        } else {
            const res = await listByIds(val as string)
            list = res.data
          // æ­¤å¤„name使用ossId é˜²æ­¢åˆ é™¤å‡ºçŽ°é‡å
          itemData = { name: item.ossId, url: item.url, ossId: item.ossId };
        }
        // ç„¶åŽå°†æ•°ç»„转为对象数组
        fileList.value = list.map(item => {
            // å­—符串回显处理 å¦‚果此处存的是url可直接回显 å¦‚果存的是id需要调用接口查出来
            let itemData;
            if (typeof item === "string") {
                itemData = { name: item, url: item };
            } else {
                // æ­¤å¤„name使用ossId é˜²æ­¢åˆ é™¤å‡ºçŽ°é‡å
                itemData = { name: item.ossId, url: item.url, ossId: item.ossId };
            }
            return itemData;
        });
        return itemData;
      });
    } else {
        fileList.value = [];
        return [];
      fileList.value = [];
      return [];
    }
}, { deep: true, immediate: true });
  },
  { deep: true, immediate: true }
);
/** ä¸Šä¼ å‰loading加载 */
const handleBeforeUpload = (file: any) => {
    let isImg = false;
    if (props.fileType.length) {
        let fileExtension = "";
        if (file.name.lastIndexOf(".") > -1) {
            fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
        }
        isImg = props.fileType.some((type: any) => {
            if (file.type.indexOf(type) > -1) return true;
            if (fileExtension && fileExtension.indexOf(type) > -1) return true;
            return false;
        });
    } else {
        isImg = file.type.indexOf("image") > -1;
  let isImg = false;
  if (props.fileType.length) {
    let fileExtension = '';
    if (file.name.lastIndexOf('.') > -1) {
      fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1);
    }
    if (!isImg) {
        proxy?.$modal.msgError(
            `文件格式不正确, è¯·ä¸Šä¼ ${props.fileType.join("/")}图片格式文件!`
        );
        return false;
    isImg = props.fileType.some((type: any) => {
      if (file.type.indexOf(type) > -1) return true;
      if (fileExtension && fileExtension.indexOf(type) > -1) return true;
      return false;
    });
  } else {
    isImg = file.type.indexOf('image') > -1;
  }
  if (!isImg) {
    proxy?.$modal.msgError(`文件格式不正确, è¯·ä¸Šä¼ ${props.fileType.join('/')}图片格式文件!`);
    return false;
  }
  if (props.fileSize) {
    const isLt = file.size / 1024 / 1024 < props.fileSize;
    if (!isLt) {
      proxy?.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);
      return false;
    }
    if (props.fileSize) {
        const isLt = file.size / 1024 / 1024 < props.fileSize;
        if (!isLt) {
            proxy?.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);
            return false;
        }
    }
    proxy?.$modal.loading("正在上传图片,请稍候...");
    number.value++;
}
  }
  proxy?.$modal.loading('正在上传图片,请稍候...');
  number.value++;
};
// æ–‡ä»¶ä¸ªæ•°è¶…出
const handleExceed = () => {
    proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} ä¸ª!`);
}
  proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} ä¸ª!`);
};
// ä¸Šä¼ æˆåŠŸå›žè°ƒ
const handleUploadSuccess = (res: any, file: UploadFile) => {
    if (res.code === 200) {
        uploadList.value.push({ name: res.data.fileName, url: res.data.url, ossId: res.data.ossId });
        uploadedSuccessfully();
    } else {
        number.value--;
        proxy?.$modal.closeLoading();
        proxy?.$modal.msgError(res.msg);
        imageUploadRef.value?.handleRemove(file);
        uploadedSuccessfully();
    }
}
  if (res.code === 200) {
    uploadList.value.push({ name: res.data.fileName, url: res.data.url, ossId: res.data.ossId });
    uploadedSuccessfully();
  } else {
    number.value--;
    proxy?.$modal.closeLoading();
    proxy?.$modal.msgError(res.msg);
    imageUploadRef.value?.handleRemove(file);
    uploadedSuccessfully();
  }
};
// åˆ é™¤å›¾ç‰‡
const handleDelete = (file: UploadFile): boolean => {
    const findex = fileList.value.map(f => f.name).indexOf(file.name);
    if (findex > -1 && uploadList.value.length === number.value) {
        let ossId = fileList.value[findex].ossId;
        delOss(ossId);
        fileList.value.splice(findex, 1);
        emit("update:modelValue", listToString(fileList.value));
        return false;
    }
    return true;
}
  const findex = fileList.value.map((f) => f.name).indexOf(file.name);
  if (findex > -1 && uploadList.value.length === number.value) {
    let ossId = fileList.value[findex].ossId;
    delOss(ossId);
    fileList.value.splice(findex, 1);
    emit('update:modelValue', listToString(fileList.value));
    return false;
  }
  return true;
};
// ä¸Šä¼ ç»“束处理
const uploadedSuccessfully = () => {
    if (number.value > 0 && uploadList.value.length === number.value) {
        fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
        uploadList.value = [];
        number.value = 0;
        emit("update:modelValue", listToString(fileList.value));
        proxy?.$modal.closeLoading();
    }
}
  if (number.value > 0 && uploadList.value.length === number.value) {
    fileList.value = fileList.value.filter((f) => f.url !== undefined).concat(uploadList.value);
    uploadList.value = [];
    number.value = 0;
    emit('update:modelValue', listToString(fileList.value));
    proxy?.$modal.closeLoading();
  }
};
// ä¸Šä¼ å¤±è´¥
const handleUploadError = () => {
    proxy?.$modal.msgError("上传图片失败");
    proxy?.$modal.closeLoading();
}
  proxy?.$modal.msgError('上传图片失败');
  proxy?.$modal.closeLoading();
};
// é¢„览
const handlePictureCardPreview = (file: any) => {
    dialogImageUrl.value = file.url;
    dialogVisible.value = true;
}
  dialogImageUrl.value = file.url;
  dialogVisible.value = true;
};
// å¯¹è±¡è½¬æˆæŒ‡å®šå­—符串分隔
const listToString = (list: any[], separator?: string) => {
    let strs = "";
    separator = separator || ",";
    for (let i in list) {
        if (undefined !== list[i].ossId && list[i].url.indexOf("blob:") !== 0) {
            strs += list[i].ossId + separator;
        }
  let strs = '';
  separator = separator || ',';
  for (let i in list) {
    if (undefined !== list[i].ossId && list[i].url.indexOf('blob:') !== 0) {
      strs += list[i].ossId + separator;
    }
    return strs != "" ? strs.substring(0, strs.length - 1) : "";
}
  }
  return strs != '' ? strs.substring(0, strs.length - 1) : '';
};
</script>
<style scoped lang="scss">
// .el-upload--picture-card æŽ§åˆ¶åŠ å·éƒ¨åˆ†
:deep(.hide .el-upload--picture-card) {
    display: none;
  display: none;
}
</style>
src/components/LangSelect/index.vue
@@ -20,16 +20,15 @@
const appStore = useAppStore();
const { locale } = useI18n();
const message: any = {
  zh_CN: '切换语言成功!',
  en_US: 'Switch Language Successful!',
}
  en_US: 'Switch Language Successful!'
};
const handleLanguageChange = (lang: string) => {
  locale.value = lang;
  appStore.changeLanguage(lang);
  ElMessage.success(message[lang] || '切换语言成功!');
}
};
</script>
<style lang="scss" scoped>
src/components/Pagination/index.vue
@@ -1,9 +1,9 @@
<template>
  <div :class="{ 'hidden': hidden }" class="pagination-container">
  <div :class="{ hidden: hidden }" class="pagination-container">
    <el-pagination
      :background="background"
      v-model:current-page="currentPage"
      v-model:page-size="pageSize"
      :background="background"
      :layout="layout"
      :page-sizes="pageSizes"
      :pager-count="pagerCount"
@@ -16,69 +16,69 @@
<script lang="ts">
export default {
    name: 'Pagination'
}
  name: 'Pagination'
};
</script>
<script setup lang="ts">
import { scrollTo } from '@/utils/scroll-to'
import { propTypes } from "@/utils/propTypes";
import { scrollTo } from '@/utils/scroll-to';
import { propTypes } from '@/utils/propTypes';
const props = defineProps({
    total: propTypes.number,
    page: propTypes.number.def(1),
    limit: propTypes.number.def(20),
    pageSizes: {
      type: Array as PropType<number[]>,
      default: () => [10, 20, 30, 50]
    },
    // ç§»åŠ¨ç«¯é¡µç æŒ‰é’®çš„æ•°é‡ç«¯é»˜è®¤å€¼5
    pagerCount: propTypes.number.def(document.body.clientWidth < 992 ? 5 : 7),
    layout: propTypes.string.def('total, sizes, prev, pager, next, jumper'),
    background: propTypes.bool.def(true),
    autoScroll: propTypes.bool.def(true),
    hidden: propTypes.bool.def(false),
    float: propTypes.string.def('right')
})
  total: propTypes.number,
  page: propTypes.number.def(1),
  limit: propTypes.number.def(20),
  pageSizes: {
    type: Array,
    default: () => [10, 20, 30, 50]
  },
  // ç§»åŠ¨ç«¯é¡µç æŒ‰é’®çš„æ•°é‡ç«¯é»˜è®¤å€¼5
  pagerCount: propTypes.number.def(document.body.clientWidth < 992 ? 5 : 7),
  layout: propTypes.string.def('total, sizes, prev, pager, next, jumper'),
  background: propTypes.bool.def(true),
  autoScroll: propTypes.bool.def(true),
  hidden: propTypes.bool.def(false),
  float: propTypes.string.def('right')
});
const emit = defineEmits(['update:page', 'update:limit', 'pagination']);
const currentPage = computed({
    get() {
        return props.page
    },
    set(val) {
        emit('update:page', val)
    }
})
  get() {
    return props.page;
  },
  set(val) {
    emit('update:page', val);
  }
});
const pageSize = computed({
    get() {
        return props.limit
    },
    set(val){
        emit('update:limit', val)
    }
})
  get() {
    return props.limit;
  },
  set(val) {
    emit('update:limit', val);
  }
});
function handleSizeChange(val: number) {
    if (currentPage.value * val > props.total) {
        currentPage.value = 1
    }
    emit('pagination', { page: currentPage.value, limit: val })
    if (props.autoScroll) {
        scrollTo(0, 800)
    }
  if (currentPage.value * val > props.total) {
    currentPage.value = 1;
  }
  emit('pagination', { page: currentPage.value, limit: val });
  if (props.autoScroll) {
    scrollTo(0, 800);
  }
}
function handleCurrentChange(val: number) {
    emit('pagination', { page: val, limit: pageSize.value })
    if (props.autoScroll) {
        scrollTo(0, 800)
    }
  emit('pagination', { page: val, limit: pageSize.value });
  if (props.autoScroll) {
    scrollTo(0, 800);
  }
}
</script>
<style lang="scss" scoped>
.pagination-container {
  padding: 32px 16px;
  .el-pagination{
  .el-pagination {
    float: v-bind(float);
  }
}
src/components/RightToolbar/index.vue
@@ -1,13 +1,13 @@
<template>
  <div class="top-right-btn" :style="style">
    <el-row>
      <el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top" v-if="search">
      <el-tooltip v-if="search" class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top">
        <el-button circle icon="Search" @click="toggleSearch()" />
      </el-tooltip>
      <el-tooltip class="item" effect="dark" content="刷新" placement="top">
        <el-button circle icon="Refresh" @click="refresh()" />
      </el-tooltip>
      <el-tooltip class="item" effect="dark" content="显示/隐藏列" placement="top" v-if="columns">
      <el-tooltip v-if="columns" class="item" effect="dark" content="显示/隐藏列" placement="top">
        <div class="show-btn">
          <el-popover placement="bottom" trigger="click">
            <div class="tree-header">显示/隐藏列</div>
@@ -15,9 +15,9 @@
              ref="columnRef"
              :data="columns"
              show-checkbox
              @check="columnChange"
              node-key="key"
              :props="{ label: 'label', children: 'children' }"
              @check="columnChange"
            ></el-tree>
            <template #reference>
              <el-button circle icon="Menu" />
@@ -33,51 +33,49 @@
import { propTypes } from '@/utils/propTypes';
const props = defineProps({
    showSearch: propTypes.bool.def(true),
    columns: {
        type: Array as PropType<FieldOption[]>,
    },
    search: propTypes.bool.def(true),
    gutter: propTypes.number.def(10),
})
  showSearch: propTypes.bool.def(true),
  columns: propTypes.fieldOption,
  search: propTypes.bool.def(true),
  gutter: propTypes.number.def(10)
});
const columnRef = ref<ElTreeInstance>();
const emits = defineEmits(['update:showSearch', 'queryTable']);
const style = computed(() => {
    const ret: any = {};
    if (props.gutter) {
        ret.marginRight = `${props.gutter / 2}px`;
    }
    return ret;
  const ret: any = {};
  if (props.gutter) {
    ret.marginRight = `${props.gutter / 2}px`;
  }
  return ret;
});
// æœç´¢
function toggleSearch() {
    emits("update:showSearch", !props.showSearch);
  emits('update:showSearch', !props.showSearch);
}
// åˆ·æ–°
function refresh() {
    emits("queryTable");
  emits('queryTable');
}
// æ›´æ”¹æ•°æ®åˆ—的显示和隐藏
function columnChange(...args: any[]) {
  props.columns?.forEach((item) => {
    item.visible = args[1].checkedKeys.includes(item.key);
  })
  });
}
// æ˜¾éšåˆ—初始默认隐藏列
onMounted(() => {
    props.columns?.forEach((item) => {
        if (item.visible) {
          columnRef.value?.setChecked(item.key, true, false);
            // value.value.push(item.key);
        }
    })
})
  props.columns?.forEach((item) => {
    if (item.visible) {
      columnRef.value?.setChecked(item.key, true, false);
      // value.value.push(item.key);
    }
  });
});
</script>
<style lang="scss" scoped>
@@ -93,7 +91,7 @@
.my-el-transfer {
  text-align: center;
}
.tree-header{
.tree-header {
  width: 100%;
  line-height: 24px;
  text-align: center;
src/components/RuoYiDoc/index.vue
@@ -8,6 +8,6 @@
const url = ref('https://plus-doc.dromara.org/');
function goto() {
  window.open(url.value)
  window.open(url.value);
}
</script>
src/components/RuoYiGit/index.vue
@@ -8,6 +8,6 @@
const url = ref('https://gitee.com/dromara/RuoYi-Vue-Plus');
function goto() {
  window.open(url.value)
  window.open(url.value);
}
</script>
src/components/SizeSelect/index.vue
@@ -16,20 +16,20 @@
</template>
<script setup lang="ts">
import useAppStore from "@/store/modules/app";
import useAppStore from '@/store/modules/app';
const appStore = useAppStore();
const size = computed(() => appStore.size);
const sizeOptions = ref([
    { label: "较大", value: "large" },
    { label: "默认", value: "default" },
    { label: "稍小", value: "small" },
  { label: '较大', value: 'large' },
  { label: '默认', value: 'default' },
  { label: '稍小', value: 'small' }
]);
const handleSetSize = (size: string) => {
    appStore.setSize(size);
}
  appStore.setSize(size);
};
</script>
<style lang="scss" scoped>
src/components/SvgIcon/index.vue
@@ -8,17 +8,17 @@
import { propTypes } from '@/utils/propTypes';
const props = defineProps({
    iconClass: propTypes.string.isRequired,
    className: propTypes.string.def(''),
    color: propTypes.string.def(''),
})
const iconName =  computed(() => `#icon-${props.iconClass}`);
  iconClass: propTypes.string.isRequired,
  className: propTypes.string.def(''),
  color: propTypes.string.def('')
});
const iconName = computed(() => `#icon-${props.iconClass}`);
const svgClass = computed(() => {
    if (props.className) {
        return `svg-icon ${props.className}`
    }
    return 'svg-icon'
})
  if (props.className) {
    return `svg-icon ${props.className}`;
  }
  return 'svg-icon';
});
</script>
<style scope lang="scss">
src/components/TopNav/index.vue
@@ -1,19 +1,18 @@
<template>
  <el-menu :default-active="activeMenu" mode="horizontal" @select="handleSelect" :ellipsis="false">
  <el-menu :default-active="activeMenu" mode="horizontal" :ellipsis="false" @select="handleSelect">
    <template v-for="(item, index) in topMenus">
      <el-menu-item :style="{'--theme': theme}" :index="item.path" :key="index" v-if="index < visibleNumber"
        ><svg-icon
        v-if="item.meta && item.meta.icon && item.meta.icon !== '#'"
        :icon-class="item.meta ? item.meta.icon : '' " /> {{ item.meta?.title }}</el-menu-item
      <el-menu-item v-if="index < visibleNumber" :key="index" :style="{ '--theme': theme }" :index="item.path"
        ><svg-icon v-if="item.meta && item.meta.icon && item.meta.icon !== '#'" :icon-class="item.meta ? item.meta.icon : ''" />
        {{ item.meta?.title }}</el-menu-item
      >
    </template>
    <!-- é¡¶éƒ¨èœå•超出数量折叠 -->
    <el-sub-menu :style="{'--theme': theme}" index="more" v-if="topMenus.length > visibleNumber">
    <el-sub-menu v-if="topMenus.length > visibleNumber" :style="{ '--theme': theme }" index="more">
      <template #title>更多菜单</template>
      <template v-for="(item, index) in topMenus">
        <el-menu-item :index="item.path" :key="index" v-if="index >= visibleNumber"
          ><svg-icon :icon-class="item.meta ? item.meta.icon : '' " /> {{ item.meta?.title }}</el-menu-item
        <el-menu-item v-if="index >= visibleNumber" :key="index" :index="item.path"
          ><svg-icon :icon-class="item.meta ? item.meta.icon : ''" /> {{ item.meta?.title }}</el-menu-item
        >
      </template>
    </el-sub-menu>
@@ -26,7 +25,7 @@
import useAppStore from '@/store/modules/app';
import useSettingsStore from '@/store/modules/settings';
import usePermissionStore from '@/store/modules/permission';
import { RouteOption } from 'vue-router';
import { RouteRecordRaw } from 'vue-router';
// é¡¶éƒ¨æ åˆå§‹æ•°
const visibleNumber = ref<number>(-1);
@@ -35,9 +34,9 @@
// éšè—ä¾§è¾¹æ è·¯ç”±
const hideList = ['/index', '/user/profile'];
const appStore = useAppStore()
const settingsStore = useSettingsStore()
const permissionStore = usePermissionStore()
const appStore = useAppStore();
const settingsStore = useSettingsStore();
const permissionStore = usePermissionStore();
const route = useRoute();
const router = useRouter();
@@ -48,73 +47,73 @@
// é¡¶éƒ¨æ˜¾ç¤ºèœå•
const topMenus = computed(() => {
  let topMenus:RouteOption[] = [];
  let topMenus: RouteRecordRaw[] = [];
  routers.value.map((menu) => {
    if (menu.hidden !== true) {
      // å…¼å®¹é¡¶éƒ¨æ ä¸€çº§èœå•内部跳转
      if (menu.path === "/") {
          topMenus.push(menu.children? menu.children[0] : menu);
      if (menu.path === '/') {
        topMenus.push(menu.children ? menu.children[0] : menu);
      } else {
          topMenus.push(menu);
        topMenus.push(menu);
      }
    }
  })
  });
  return topMenus;
})
});
// è®¾ç½®å­è·¯ç”±
const childrenMenus = computed(() => {
  let childrenMenus:RouteOption[] = [];
  let childrenMenus: RouteRecordRaw[] = [];
  routers.value.map((router) => {
    router.children?.forEach((item) => {
      if (item.parentPath === undefined) {
        if(router.path === "/") {
          item.path = "/" + item.path;
        if (router.path === '/') {
          item.path = '/' + item.path;
        } else {
          if(!isHttp(item.path)) {
            item.path = router.path + "/" + item.path;
          if (!isHttp(item.path)) {
            item.path = router.path + '/' + item.path;
          }
        }
        item.parentPath = router.path;
      }
      childrenMenus.push(item);
    })
  })
    });
  });
  return constantRoutes.concat(childrenMenus);
})
});
// é»˜è®¤æ¿€æ´»çš„菜单
const activeMenu = computed(() => {
  const path = route.path;
  let activePath = path;
  if (path !== undefined && path.lastIndexOf("/") > 0 && hideList.indexOf(path) === -1) {
  if (path !== undefined && path.lastIndexOf('/') > 0 && hideList.indexOf(path) === -1) {
    const tmpPath = path.substring(1, path.length);
    activePath = "/" + tmpPath.substring(0, tmpPath.indexOf("/"));
    activePath = '/' + tmpPath.substring(0, tmpPath.indexOf('/'));
    if (!route.meta.link) {
        appStore.toggleSideBarHide(false);
      appStore.toggleSideBarHide(false);
    }
  } else if(!route.children) {
  } else if (!route.children) {
    activePath = path;
    appStore.toggleSideBarHide(true);
  }
  activeRoutes(activePath);
  return activePath;
})
});
const setVisibleNumber = () => {
  const width = document.body.getBoundingClientRect().width / 3;
  visibleNumber.value = parseInt(String(width / 85));
}
};
const handleSelect = (key: string) => {
  currentIndex.value = key;
  const route = routers.value.find(item => item.path === key);
  const route = routers.value.find((item) => item.path === key);
  if (isHttp(key)) {
    // http(s):// è·¯å¾„新窗口打开
    window.open(key, "_blank");
    window.open(key, '_blank');
  } else if (!route || !route.children) {
    // æ²¡æœ‰å­è·¯ç”±è·¯å¾„内部打开
    const routeMenu = childrenMenus.value.find(item => item.path === key);
    const routeMenu = childrenMenus.value.find((item) => item.path === key);
    if (routeMenu && routeMenu.query) {
      let query = JSON.parse(routeMenu.query);
      router.push({ path: key, query: query });
@@ -127,35 +126,35 @@
    activeRoutes(key);
    appStore.toggleSideBarHide(false);
  }
}
};
const activeRoutes = (key: string) => {
  let routes:RouteOption[] = [];
  let routes: RouteRecordRaw[] = [];
  if (childrenMenus.value && childrenMenus.value.length > 0) {
    childrenMenus.value.map((item) => {
      if (key == item.parentPath || (key == "index" && "" == item.path)) {
      if (key == item.parentPath || (key == 'index' && '' == item.path)) {
        routes.push(item);
      }
    });
  }
  if(routes.length > 0) {
  if (routes.length > 0) {
    permissionStore.setSidebarRouters(routes);
  } else {
    appStore.toggleSideBarHide(true);
  }
  return routes;
}
};
onMounted(() => {
  window.addEventListener('resize', setVisibleNumber)
})
  window.addEventListener('resize', setVisibleNumber);
});
onBeforeUnmount(() => {
  window.removeEventListener('resize', setVisibleNumber)
})
  window.removeEventListener('resize', setVisibleNumber);
});
onMounted(() => {
  setVisibleNumber()
})
  setVisibleNumber();
});
</script>
<style lang="scss">
@@ -168,7 +167,8 @@
  margin: 0 10px !important;
}
.topmenu-container.el-menu--horizontal > .el-menu-item.is-active, .el-menu--horizontal > .el-sub-menu.is-active .el-submenu__title {
.topmenu-container.el-menu--horizontal > .el-menu-item.is-active,
.el-menu--horizontal > .el-sub-menu.is-active .el-submenu__title {
  border-bottom: 2px solid #{'var(--theme)'} !important;
  color: #303133;
}
@@ -184,7 +184,9 @@
}
/* èƒŒæ™¯è‰²éšè— */
.topmenu-container.el-menu--horizontal>.el-menu-item:not(.is-disabled):focus, .topmenu-container.el-menu--horizontal>.el-menu-item:not(.is-disabled):hover, .topmenu-container.el-menu--horizontal>.el-submenu .el-submenu__title:hover {
.topmenu-container.el-menu--horizontal > .el-menu-item:not(.is-disabled):focus,
.topmenu-container.el-menu--horizontal > .el-menu-item:not(.is-disabled):hover,
.topmenu-container.el-menu--horizontal > .el-submenu .el-submenu__title:hover {
  background-color: #ffffff !important;
}
src/components/TreeSelect/index.vue
@@ -1,14 +1,14 @@
<template>
  <div class="el-tree-select">
    <el-select
      style="width: 100%"
      v-model="valueId"
      ref="treeSelect"
      v-model="valueId"
      style="width: 100%"
      :filterable="true"
      :clearable="true"
      @clear="clearHandle"
      :filter-method="selectFilterData"
      :placeholder="placeholder"
      @clear="clearHandle"
    >
      <el-option :value="valueId" :label="valueTitle">
        <el-tree
@@ -29,43 +29,32 @@
</template>
<script setup lang="ts">
interface ObjMap {
  value: string;
  label: string;
  children: string;
}
interface Props {
  objMap: ObjMap;
  accordion: boolean;
  value: string | number;
  options: any[];
  placeholder: string;
}
const props = defineProps({
  /* é…ç½®é¡¹ */
  objMap: {
    type: Object,
    default: () => {
      return {
        value: 'id', // ID字段名
        label: 'label', // æ˜¾ç¤ºåç§°
        children: 'children' // å­çº§å­—段名
      }
    }
const props = withDefaults(defineProps<Props>(), {
  objMap: () => {
    return {
      value: 'id',
      label: 'label',
      children: 'children'
    };
  },
  /* è‡ªåŠ¨æ”¶èµ· */
  accordion: {
    type: Boolean,
    default: () => {
      return false
    }
  },
  /**当前双向数据绑定的值 */
  value: {
    type: [String, Number],
    default: ''
  },
  /**当前的数据 */
  options: {
    type: Array,
    default: () => []
  },
  /**输入框内部的文字 */
  placeholder: {
    type: String,
    default: ''
  }
})
  accordion: false,
  value: '',
  options: () => [],
  placeholder: ''
});
const selectTree = ref<ElTreeSelectInstance>();
@@ -74,7 +63,7 @@
const valueId = computed({
  get: () => props.value,
  set: (val) => {
    emit('update:value', val)
    emit('update:value', val);
  }
});
const valueTitle = ref('');
@@ -83,54 +72,54 @@
const initHandle = () => {
  nextTick(() => {
    const selectedValue = valueId.value;
    if (selectedValue !== null && typeof (selectedValue) !== 'undefined') {
      const node = selectTree.value?.getNode(selectedValue)
    if (selectedValue !== null && typeof selectedValue !== 'undefined') {
      const node = selectTree.value?.getNode(selectedValue);
      if (node) {
        valueTitle.value = node.data[props.objMap.label]
        selectTree.value?.setCurrentKey(selectedValue) // è®¾ç½®é»˜è®¤é€‰ä¸­
        defaultExpandedKey.value = [selectedValue] // è®¾ç½®é»˜è®¤å±•å¼€
        valueTitle.value = node.data[props.objMap.label];
        selectTree.value?.setCurrentKey(selectedValue); // è®¾ç½®é»˜è®¤é€‰ä¸­
        defaultExpandedKey.value = [selectedValue]; // è®¾ç½®é»˜è®¤å±•å¼€
      }
    } else {
      clearHandle()
      clearHandle();
    }
  })
}
  });
};
const handleNodeClick = (node: any) => {
  valueTitle.value = node[props.objMap.label]
  valueTitle.value = node[props.objMap.label];
  valueId.value = node[props.objMap.value];
  defaultExpandedKey.value = [];
  selectTree.value?.blur()
  selectFilterData('')
}
  selectTree.value?.blur();
  selectFilterData('');
};
const selectFilterData = (val: any) => {
  selectTree.value?.filter(val)
}
  selectTree.value?.filter(val);
};
const filterNode = (value: any, data: any) => {
  if (!value) return true
  return data[props.objMap['label']].indexOf(value) !== -1
}
  if (!value) return true;
  return data[props.objMap['label']].indexOf(value) !== -1;
};
const clearHandle = () => {
  valueTitle.value = ''
  valueId.value = ''
  valueTitle.value = '';
  valueId.value = '';
  defaultExpandedKey.value = [];
  clearSelected()
}
  clearSelected();
};
const clearSelected = () => {
  const allNode = document.querySelectorAll('#tree-option .el-tree-node')
  allNode.forEach((element) => element.classList.remove('is-current'))
}
  const allNode = document.querySelectorAll('#tree-option .el-tree-node');
  allNode.forEach((element) => element.classList.remove('is-current'));
};
onMounted(() => {
  initHandle()
})
  initHandle();
});
watch(valueId, () => {
  initHandle();
})
});
</script>
<style lang="scss" scoped>
@import "@/assets/styles/variables.module.scss";
@import '@/assets/styles/variables.module.scss';
.el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
  padding: 0;
src/components/iFrame/index.vue
@@ -9,18 +9,18 @@
const props = defineProps({
  src: propTypes.string.isRequired
})
});
const height = ref(document.documentElement.clientHeight - 94.5 + "px;")
const loading = ref(true)
const url = computed(() => props.src)
const height = ref(document.documentElement.clientHeight - 94.5 + 'px;');
const loading = ref(true);
const url = computed(() => props.src);
onMounted(() => {
  setTimeout(() => {
    loading.value = false;
  }, 300);
  window.onresize = function temp() {
    height.value = document.documentElement.clientHeight - 94.5 + "px;";
    height.value = document.documentElement.clientHeight - 94.5 + 'px;';
  };
})
});
</script>
src/directive/permission/index.ts
@@ -9,7 +9,7 @@
    // ã€Œå…¶ä»–角色」按钮权限校验
    const { value } = binding;
    if (value && value instanceof Array && value.length > 0) {
      const hasPermission = permissions.some((permi) => {
      const hasPermission = permissions.some((permi: string) => {
        return permi === '*:*:*' || value.includes(permi);
      });
      if (!hasPermission) {
@@ -30,7 +30,7 @@
    const { value } = binding;
    const { roles } = useUserStore();
    if (value && value instanceof Array && value.length > 0) {
      const hasRole = roles.some((role) => {
      const hasRole = roles.some((role: string) => {
        return role === 'admin' || value.includes(role);
      });
      if (!hasRole) {
src/layout/components/AppMain.vue
@@ -3,7 +3,7 @@
    <router-view v-slot="{ Component, route }">
      <transition :enter-active-class="animante" mode="out-in">
        <keep-alive :include="tagsViewStore.cachedViews">
          <component v-if="!route.meta.link" :is="Component" :key="route.path" />
          <component :is="Component" v-if="!route.meta.link" :key="route.path" />
        </keep-alive>
      </transition>
    </router-view>
@@ -14,22 +14,25 @@
<script setup name="AppMain" lang="ts">
import useTagsViewStore from '@/store/modules/tagsView';
import useSettingsStore from '@/store/modules/settings';
import IframeToggle  from './IframeToggle/index.vue'
import { ComponentInternalInstance } from "vue";
import IframeToggle from './IframeToggle/index.vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const tagsViewStore = useTagsViewStore();
// éšæœºåŠ¨ç”»é›†åˆ
const animante = ref<string>('');
const animationEnable = ref(useSettingsStore().animationEnable);
watch(()=> useSettingsStore().animationEnable, (val) => {
watch(
  () => useSettingsStore().animationEnable,
  (val) => {
    animationEnable.value = val;
    if (val) {
        animante.value = proxy?.animate.animateList[Math.round(Math.random() * proxy?.animate.animateList.length)] as string;
      animante.value = proxy?.animate.animateList[Math.round(Math.random() * proxy?.animate.animateList.length)] as string;
    } else {
        animante.value = proxy?.animate.defaultAnimate as string;
      animante.value = proxy?.animate.defaultAnimate as string;
    }
}, { immediate: true });
  },
  { immediate: true }
);
</script>
<style lang="scss" scoped>
@@ -41,7 +44,7 @@
  overflow: hidden;
}
.fixed-header+.app-main {
.fixed-header + .app-main {
  padding-top: 50px;
}
@@ -51,7 +54,7 @@
    min-height: calc(100vh - 84px);
  }
  .fixed-header+.app-main {
  .fixed-header + .app-main {
    padding-top: 84px;
  }
}
src/layout/components/IframeToggle/index.vue
@@ -2,16 +2,16 @@
  <transition-group name="fade-transform" mode="out-in">
    <inner-link
      v-for="(item, index) in tagsViewStore.iframeViews"
      :key="item.path"
      :iframeId="'iframe' + index"
      v-show="route.path === item.path"
      :key="item.path"
      :iframe-id="'iframe' + index"
      :src="iframeUrl(item.meta ? item.meta.link : '', item.query)"
    ></inner-link>
  </transition-group>
</template>
<script setup lang="ts">
import InnerLink from "../InnerLink/index.vue";
import InnerLink from '../InnerLink/index.vue';
import useTagsViewStore from '@/store/modules/tagsView';
const route = useRoute();
@@ -19,8 +19,10 @@
function iframeUrl(url: string, query: any) {
  if (Object.keys(query).length > 0) {
    let params = Object.keys(query).map((key) => key + "=" + query[key]).join("&");
    return url + "?" + params;
    let params = Object.keys(query)
      .map((key) => key + '=' + query[key])
      .join('&');
    return url + '?' + params;
  }
  return url;
}
src/layout/components/InnerLink/index.vue
@@ -5,14 +5,11 @@
</template>
<script setup lang="ts">
import { propTypes } from '@/utils/propTypes';
const props = defineProps({
    src: {
        type: String,
        default: "/"
    },
    iframeId: {
        type: String
    }
  src: propTypes.string.def('/'),
  iframeId: propTypes.string.isRequired
});
const height = ref(document.documentElement.clientHeight - 94.5 + "px");
const height = ref(document.documentElement.clientHeight - 94.5 + 'px');
</script>
src/layout/components/Navbar.vue
@@ -1,18 +1,18 @@
<template>
  <div class="navbar">
    <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
    <breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!settingsStore.topNav" />
    <top-nav id="topmenu-container" class="topmenu-container" v-if="settingsStore.topNav" />
    <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggle-click="toggleSideBar" />
    <breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container" />
    <top-nav v-if="settingsStore.topNav" id="topmenu-container" class="topmenu-container" />
    <div class="right-menu flex align-center">
      <template v-if="appStore.device !== 'mobile'">
        <el-select
          v-if="userId === 1 && tenantEnabled"
          v-model="companyName"
          clearable
          filterable
          reserve-keyword
          :placeholder="$t('navbar.selectTenant')"
          v-if="userId === 1 && tenantEnabled"
          @change="dynamicTenantEvent"
          @clear="dynamicClearEvent"
        >
@@ -63,17 +63,17 @@
        </el-tooltip>
      </template>
      <div class="avatar-container">
        <el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
        <el-dropdown class="right-menu-item hover-effect" trigger="click" @command="handleCommand">
          <div class="avatar-wrapper">
            <img :src="userStore.avatar" class="user-avatar" />
            <el-icon><caret-bottom /></el-icon>
          </div>
          <template #dropdown>
            <el-dropdown-menu>
              <router-link to="/user/profile" v-if="!dynamic">
              <router-link v-if="!dynamic" to="/user/profile">
                <el-dropdown-item>{{ $t('navbar.personalCenter') }}</el-dropdown-item>
              </router-link>
              <el-dropdown-item command="setLayout" v-if="settingsStore.showSettings">
              <el-dropdown-item v-if="settingsStore.showSettings" command="setLayout">
                <span>{{ $t('navbar.layoutSetting') }}</span>
              </el-dropdown-item>
              <el-dropdown-item divided command="logout">
@@ -92,10 +92,9 @@
import useAppStore from '@/store/modules/app';
import useUserStore from '@/store/modules/user';
import useSettingsStore from '@/store/modules/settings';
import { getTenantList } from "@/api/login";
import { dynamicClear, dynamicTenant } from "@/api/system/tenant";
import { ComponentInternalInstance } from "vue";
import { TenantVO } from "@/api/types";
import { getTenantList } from '@/api/login';
import { dynamicClear, dynamicTenant } from '@/api/system/tenant';
import { TenantVO } from '@/api/types';
import notice from './notice/index.vue';
import useNoticeStore from '@/store/modules/notice';
@@ -119,7 +118,7 @@
const openSearchMenu = () => {
  searchMenuRef.value?.openSearch();
}
};
// åŠ¨æ€åˆ‡æ¢
const dynamicTenantEvent = async (tenantId: string) => {
@@ -129,14 +128,14 @@
    proxy?.$tab.closeAllPage();
    proxy?.$router.push('/');
  }
}
};
const dynamicClearEvent = async () => {
  await dynamicClear();
  dynamic.value = false;
  proxy?.$tab.closeAllPage();
  proxy?.$router.push('/');
}
};
/** ç§Ÿæˆ·åˆ—表 */
const initTenantList = async () => {
@@ -145,56 +144,58 @@
  if (tenantEnabled.value) {
    tenantList.value = data.voList;
  }
}
};
defineExpose({
  initTenantList,
})
  initTenantList
});
const toggleSideBar = () => {
  appStore.toggleSideBar(false);
}
};
const logout = async () => {
    await ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    await userStore.logout()
    location.href = import.meta.env.VITE_APP_CONTEXT_PATH + 'index';
}
  await ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  });
  await userStore.logout();
  location.href = import.meta.env.VITE_APP_CONTEXT_PATH + 'index';
};
const emits = defineEmits(['setLayout'])
const emits = defineEmits(['setLayout']);
const setLayout = () => {
    emits('setLayout');
}
  emits('setLayout');
};
// å®šä¹‰Command方法对象 é€šè¿‡key直接调用方法
const commandMap: {[key: string]: any} = {
    setLayout,
    logout
const commandMap: { [key: string]: any } = {
  setLayout,
  logout
};
const handleCommand = (command: string) => {
    // åˆ¤æ–­æ˜¯å¦å­˜åœ¨è¯¥æ–¹æ³•
    if (commandMap[command]) {
        commandMap[command]();
    }
}
  // åˆ¤æ–­æ˜¯å¦å­˜åœ¨è¯¥æ–¹æ³•
  if (commandMap[command]) {
    commandMap[command]();
  }
};
//用深度监听 æ¶ˆæ¯
watch(() => noticeStore.state.value.notices, (newVal, oldVal) => {
  newNotice.value = newVal.filter((item: any) => !item.read).length;
}, { deep: true });
watch(
  () => noticeStore.state.value.notices,
  (newVal) => {
    newNotice.value = newVal.filter((item: any) => !item.read).length;
  },
  { deep: true }
);
</script>
<style lang="scss" scoped>
:deep(.el-select .el-input__wrapper) {
  height:30px;
  height: 30px;
}
:deep(.el-badge__content.is-fixed){
    top: 12px;
:deep(.el-badge__content.is-fixed) {
  top: 12px;
}
.flex {
src/layout/components/Settings/index.vue
@@ -1,11 +1,11 @@
<template>
  <el-drawer v-model="showSettings" :withHeader="false" direction="rtl" size="300px" close-on-click-modal>
  <el-drawer v-model="showSettings" :with-header="false" direction="rtl" size="300px" close-on-click-modal>
    <h3 class="drawer-title">主题风格设置</h3>
    <div class="setting-drawer-block-checbox">
      <div class="setting-drawer-block-checbox-item" @click="handleTheme(SideThemeEnum.DARK)">
        <img src="@/assets/images/dark.svg" alt="dark" />
        <div v-if="sideTheme === 'theme-dark'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
        <div v-if="sideTheme === 'theme-dark'" class="setting-drawer-block-checbox-selectIcon" style="display: block">
          <i aria-label="图标: check" class="anticon anticon-check">
            <svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class>
              <path
@@ -17,7 +17,7 @@
      </div>
      <div class="setting-drawer-block-checbox-item" @click="handleTheme(SideThemeEnum.LIGHT)">
        <img src="@/assets/images/light.svg" alt="light" />
        <div v-if="sideTheme === 'theme-light'" class="setting-drawer-block-checbox-selectIcon" style="display: block;">
        <div v-if="sideTheme === 'theme-light'" class="setting-drawer-block-checbox-selectIcon" style="display: block">
          <i aria-label="图标: check" class="anticon anticon-check">
            <svg viewBox="64 64 896 896" data-icon="check" width="1em" height="1em" :fill="theme" aria-hidden="true" focusable="false" class>
              <path
@@ -37,7 +37,7 @@
    <div class="drawer-item">
      <span>深色模式</span>
      <span class="comp-style">
        <el-switch v-model="isDark" @change="toggleDark" class="drawer-switch" />
        <el-switch v-model="isDark" class="drawer-switch" @change="toggleDark" />
      </span>
    </div>
@@ -88,126 +88,126 @@
</template>
<script setup lang="ts">
import { useDynamicTitle } from '@/utils/dynamicTitle'
import useAppStore from '@/store/modules/app'
import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission'
import { handleThemeStyle } from '@/utils/theme'
import { ComponentInternalInstance } from "vue";
import { SettingTypeEnum } from "@/enums/SettingTypeEnum";
import { SideThemeEnum } from "@/enums/SideThemeEnum";
import { useDynamicTitle } from '@/utils/dynamicTitle';
import useAppStore from '@/store/modules/app';
import useSettingsStore from '@/store/modules/settings';
import usePermissionStore from '@/store/modules/permission';
import { handleThemeStyle } from '@/utils/theme';
import { SettingTypeEnum } from '@/enums/SettingTypeEnum';
import { SideThemeEnum } from '@/enums/SideThemeEnum';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const appStore = useAppStore()
const settingsStore = useSettingsStore()
const permissionStore = usePermissionStore()
const appStore = useAppStore();
const settingsStore = useSettingsStore();
const permissionStore = usePermissionStore();
const showSettings = ref(false);
const theme = ref(settingsStore.theme);
const sideTheme = ref(settingsStore.sideTheme);
const storeSettings = computed(() => settingsStore);
const predefineColors = ref(["#409EFF", "#ff4500", "#ff8c00", "#ffd700", "#90ee90", "#00ced1", "#1e90ff", "#c71585"]);
const predefineColors = ref(['#409EFF', '#ff4500', '#ff8c00', '#ffd700', '#90ee90', '#00ced1', '#1e90ff', '#c71585']);
// æ˜¯å¦æš—黑模式
const isDark = useDark({
  storageKey: 'useDarkKey',
  valueDark: 'dark',
  valueLight: 'light',
  valueLight: 'light'
});
watch(isDark, ()=> {
watch(isDark, () => {
  if (isDark.value) {
    settingsStore.changeSetting({ key: SettingTypeEnum.SIDE_THEME, value: SideThemeEnum.DARK })
    settingsStore.changeSetting({ key: SettingTypeEnum.SIDE_THEME, value: SideThemeEnum.DARK });
  } else {
    settingsStore.changeSetting({ key: SettingTypeEnum.SIDE_THEME, value: sideTheme.value })
    settingsStore.changeSetting({ key: SettingTypeEnum.SIDE_THEME, value: sideTheme.value });
  }
})
});
const toggleDark = () => useToggle(isDark);
/** æ˜¯å¦éœ€è¦topNav */
const topNav = computed({
    get: () => storeSettings.value.topNav,
    set: (val) => {
        settingsStore.changeSetting({ key: SettingTypeEnum.TOP_NAV, value: val })
        if (!val) {
            appStore.toggleSideBarHide(false);
            permissionStore.setSidebarRouters(permissionStore.defaultRoutes);
        }
  get: () => storeSettings.value.topNav,
  set: (val) => {
    settingsStore.changeSetting({ key: SettingTypeEnum.TOP_NAV, value: val });
    if (!val) {
      appStore.toggleSideBarHide(false);
      permissionStore.setSidebarRouters(permissionStore.defaultRoutes);
    }
})
  }
});
/** æ˜¯å¦éœ€è¦tagview */
const tagsView = computed({
    get: () => storeSettings.value.tagsView,
    set: (val) => {
        settingsStore.changeSetting({ key: SettingTypeEnum.TAGS_VIEW, value: val })
    }
})
  get: () => storeSettings.value.tagsView,
  set: (val) => {
    settingsStore.changeSetting({ key: SettingTypeEnum.TAGS_VIEW, value: val });
  }
});
/**是否需要固定头部 */
const fixedHeader = computed({
    get: () => storeSettings.value.fixedHeader,
    set: (val) => {
        settingsStore.changeSetting({ key: SettingTypeEnum.FIXED_HEADER, value: val })
    }
})
  get: () => storeSettings.value.fixedHeader,
  set: (val) => {
    settingsStore.changeSetting({ key: SettingTypeEnum.FIXED_HEADER, value: val });
  }
});
/**是否需要侧边栏的logo */
const sidebarLogo = computed({
    get: () => storeSettings.value.sidebarLogo,
    set: (val) => {
        settingsStore.changeSetting({ key: SettingTypeEnum.SIDEBAR_LOGO, value: val })
    }
})
  get: () => storeSettings.value.sidebarLogo,
  set: (val) => {
    settingsStore.changeSetting({ key: SettingTypeEnum.SIDEBAR_LOGO, value: val });
  }
});
/**是否需要侧边栏的动态网页的title */
const dynamicTitle = computed({
    get: () => storeSettings.value.dynamicTitle,
    set: (val) => {
        settingsStore.changeSetting({ key: SettingTypeEnum.DYNAMIC_TITLE, value: val })
        // åŠ¨æ€è®¾ç½®ç½‘é¡µæ ‡é¢˜
        useDynamicTitle()
    }
})
  get: () => storeSettings.value.dynamicTitle,
  set: (val) => {
    settingsStore.changeSetting({ key: SettingTypeEnum.DYNAMIC_TITLE, value: val });
    // åŠ¨æ€è®¾ç½®ç½‘é¡µæ ‡é¢˜
    useDynamicTitle();
  }
});
const themeChange = (val: string | null) => {
    settingsStore.changeSetting({ key: SettingTypeEnum.THEME, value: val })
    theme.value = val;
    if (val) {
        handleThemeStyle(val);
    }
}
const themeChange = (val: string) => {
  settingsStore.changeSetting({ key: SettingTypeEnum.THEME, value: val });
  theme.value = val;
  if (val) {
    handleThemeStyle(val);
  }
};
const handleTheme = (val: string) => {
    sideTheme.value = val;
    if (isDark.value && val === SideThemeEnum.LIGHT) {
      // æš—黑模式颜色不变
      settingsStore.changeSetting({ key: SettingTypeEnum.SIDE_THEME, value: SideThemeEnum.DARK })
      return
    }
    settingsStore.changeSetting({ key: SettingTypeEnum.SIDE_THEME, value: val })
}
  sideTheme.value = val;
  if (isDark.value && val === SideThemeEnum.LIGHT) {
    // æš—黑模式颜色不变
    settingsStore.changeSetting({ key: SettingTypeEnum.SIDE_THEME, value: SideThemeEnum.DARK });
    return;
  }
  settingsStore.changeSetting({ key: SettingTypeEnum.SIDE_THEME, value: val });
};
const saveSetting = () => {
    proxy?.$modal.loading("正在保存到本地,请稍候...");
    let layoutSetting = {
        "topNav": storeSettings.value.topNav,
        "tagsView": storeSettings.value.tagsView,
        "fixedHeader": storeSettings.value.fixedHeader,
        "sidebarLogo": storeSettings.value.sidebarLogo,
        "dynamicTitle": storeSettings.value.dynamicTitle,
        "sideTheme": storeSettings.value.sideTheme,
        "theme": storeSettings.value.theme
    };
    localStorage.setItem("layout-setting", JSON.stringify(layoutSetting));
    setTimeout(() => {proxy?.$modal.closeLoading()}, 1000)
}
  proxy?.$modal.loading('正在保存到本地,请稍候...');
  let layoutSetting = {
    topNav: storeSettings.value.topNav,
    tagsView: storeSettings.value.tagsView,
    fixedHeader: storeSettings.value.fixedHeader,
    sidebarLogo: storeSettings.value.sidebarLogo,
    dynamicTitle: storeSettings.value.dynamicTitle,
    sideTheme: storeSettings.value.sideTheme,
    theme: storeSettings.value.theme
  };
  localStorage.setItem('layout-setting', JSON.stringify(layoutSetting));
  setTimeout(() => {
    proxy?.$modal.closeLoading();
  }, 1000);
};
const resetSetting = () => {
    proxy?.$modal.loading("正在清除设置缓存并刷新,请稍候...");
    localStorage.removeItem("layout-setting")
    setTimeout("window.location.reload()", 1000)
}
  proxy?.$modal.loading('正在清除设置缓存并刷新,请稍候...');
  localStorage.removeItem('layout-setting');
  setTimeout('window.location.reload()', 1000);
};
const openSetting = () => {
    showSettings.value = true;
}
  showSettings.value = true;
};
defineExpose({
    openSetting,
})
  openSetting
});
</script>
<style lang="scss" scoped>
src/layout/components/Sidebar/Link.vue
@@ -5,36 +5,36 @@
</template>
<script setup lang="ts">
import { isExternal } from '@/utils/validate'
import { isExternal } from '@/utils/validate';
const props = defineProps({
    to: {
        type: [String, Object],
        required: true
    }
})
  to: {
    type: [String, Object],
    required: true
  }
});
const isExt = computed(() => {
    return isExternal(props.to as string)
})
  return isExternal(props.to as string);
});
const type = computed(() => {
    if (isExt.value) {
        return 'a'
    }
    return 'router-link'
})
  if (isExt.value) {
    return 'a';
  }
  return 'router-link';
});
function linkProps() {
    if (isExt.value) {
        return {
            href: props.to,
            target: '_blank',
            rel: 'noopener'
        }
    }
  if (isExt.value) {
    return {
        to: props.to
    }
      href: props.to,
      target: '_blank',
      rel: 'noopener'
    };
  }
  return {
    to: props.to
  };
}
</script>
src/layout/components/Sidebar/Logo.vue
@@ -1,7 +1,7 @@
<template>
  <div
    class="sidebar-logo-container"
    :class="{ 'collapse': collapse }"
    :class="{ collapse: collapse }"
    :style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }"
  >
    <transition :enter-active-class="proxy?.animate.logoAnimate.enter" mode="out-in">
@@ -22,18 +22,17 @@
</template>
<script setup lang="ts">
import variables from '@/assets/styles/variables.module.scss'
import logo from '@/assets/logo/logo.png'
import useSettingsStore from '@/store/modules/settings'
import { ComponentInternalInstance } from "vue";
import variables from '@/assets/styles/variables.module.scss';
import logo from '@/assets/logo/logo.png';
import useSettingsStore from '@/store/modules/settings';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
defineProps({
    collapse: {
        type: Boolean,
        required: true
    }
})
  collapse: {
    type: Boolean,
    required: true
  }
});
const title = ref('RuoYi-Vue-Plus');
const settingsStore = useSettingsStore();
@@ -77,7 +76,12 @@
      font-weight: 600;
      line-height: 50px;
      font-size: 14px;
      font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
      font-family:
        Avenir,
        Helvetica Neue,
        Arial,
        Helvetica,
        sans-serif;
      vertical-align: middle;
    }
  }
src/layout/components/Sidebar/SidebarItem.vue
@@ -13,7 +13,7 @@
    <el-sub-menu v-else ref="subMenu" :index="resolvePath(item.path)" teleported>
      <template v-if="item.meta" #title>
        <svg-icon :icon-class="item.meta ? item.meta.icon : '' " />
        <svg-icon :icon-class="item.meta ? item.meta.icon : ''" />
        <span class="menu-title" :title="hasTitle(item.meta?.title)">{{ item.meta?.title }}</span>
      </template>
@@ -30,79 +30,75 @@
</template>
<script setup lang="ts">
import { isExternal } from '@/utils/validate'
import AppLink from './Link.vue'
import { getNormalPath } from '@/utils/ruoyi'
import { RouteOption } from "vue-router";
import { isExternal } from '@/utils/validate';
import AppLink from './Link.vue';
import { getNormalPath } from '@/utils/ruoyi';
import { RouteRecordRaw } from 'vue-router';
const props = defineProps({
    // route object
    item: {
        type: Object as PropType<RouteOption>,
        required: true
    },
    isNest: {
        type: Boolean,
        default: false
    },
    basePath: {
        type: String,
        default: ''
    }
})
  // route object
  item: {
    type: Object as PropType<RouteRecordRaw>,
    required: true
  },
  isNest: {
    type: Boolean,
    default: false
  },
  basePath: {
    type: String,
    default: ''
  }
});
const onlyOneChild = ref<any>({});
const hasOneShowingChild = (parent: RouteOption, children?:RouteOption[]) => {
    if (!children) {
        children = [];
const hasOneShowingChild = (parent: RouteRecordRaw, children?: RouteRecordRaw[]) => {
  if (!children) {
    children = [];
  }
  const showingChildren = children.filter((item) => {
    if (item.hidden) {
      return false;
    } else {
      // Temp set(will be used if only has one showing child)
      onlyOneChild.value = item;
      return true;
    }
    const showingChildren = children.filter(item => {
        if (item.hidden) {
            return false
        } else {
            // Temp set(will be used if only has one showing child)
            onlyOneChild.value = item
            return true
        }
    })
  });
    // When there is only one child router, the child router is displayed by default
    if (showingChildren.length === 1) {
        return true
    }
  // When there is only one child router, the child router is displayed by default
  if (showingChildren.length === 1) {
    return true;
  }
    // Show parent if there are no child router to display
    if (showingChildren.length === 0) {
        onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
        if (parent.name === '2222') {
          console.log(onlyOneChild.value)
        }
        return true
    }
  // Show parent if there are no child router to display
  if (showingChildren.length === 0) {
    onlyOneChild.value = { ...parent, path: '', noShowingChildren: true };
    return true;
  }
    return false
  return false;
};
const resolvePath = (routePath:string, routeQuery?:string): any => {
    if (isExternal(routePath)) {
        return routePath
    }
    if (isExternal(props.basePath)) {
        return props.basePath
    }
    if (routeQuery) {
        let query = JSON.parse(routeQuery);
        return { path: getNormalPath(props.basePath + '/' + routePath), query: query }
    }
    return getNormalPath(props.basePath + '/' + routePath)
}
const resolvePath = (routePath: string, routeQuery?: string): any => {
  if (isExternal(routePath)) {
    return routePath;
  }
  if (isExternal(props.basePath)) {
    return props.basePath;
  }
  if (routeQuery) {
    let query = JSON.parse(routeQuery);
    return { path: getNormalPath(props.basePath + '/' + routePath), query: query };
  }
  return getNormalPath(props.basePath + '/' + routePath);
};
const hasTitle = (title: string | undefined): string => {
    if(!title || title.length <= 5) {
        return "";
    }
    return title;
}
  if (!title || title.length <= 5) {
    return '';
  }
  return title;
};
</script>
src/layout/components/Sidebar/index.vue
@@ -21,35 +21,34 @@
</template>
<script setup lang="ts">
import Logo from './Logo.vue'
import SidebarItem from './SidebarItem.vue'
import variables from '@/assets/styles/variables.module.scss'
import useAppStore from '@/store/modules/app'
import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission'
import { RouteOption } from "vue-router";
import Logo from './Logo.vue';
import SidebarItem from './SidebarItem.vue';
import variables from '@/assets/styles/variables.module.scss';
import useAppStore from '@/store/modules/app';
import useSettingsStore from '@/store/modules/settings';
import usePermissionStore from '@/store/modules/permission';
import { RouteRecordRaw } from 'vue-router';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const route = useRoute();
const appStore = useAppStore()
const settingsStore = useSettingsStore()
const permissionStore = usePermissionStore()
const sidebarRouters =  computed<RouteOption[]>(() => permissionStore.sidebarRouters);
const appStore = useAppStore();
const settingsStore = useSettingsStore();
const permissionStore = usePermissionStore();
const sidebarRouters = computed<RouteRecordRaw[]>(() => permissionStore.sidebarRouters);
const showLogo = computed(() => settingsStore.sidebarLogo);
const sideTheme = computed(() => settingsStore.sideTheme);
const theme = computed(() => settingsStore.theme);
const isCollapse = computed(() => !appStore.sidebar.opened);
const activeMenu = computed(() => {
    const { meta, path } = route;
    // if set path, the sidebar will highlight the path you set
    if (meta.activeMenu) {
        return meta.activeMenu;
    }
    return path;
})
  const { meta, path } = route;
  // if set path, the sidebar will highlight the path you set
  if (meta.activeMenu) {
    return meta.activeMenu;
  }
  return path;
});
const bgColor = computed(() => sideTheme.value === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground);
const textColor = computed(() => sideTheme.value === 'theme-dark' ? variables.menuColor : variables.menuLightColor);
const bgColor = computed(() => (sideTheme.value === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground));
const textColor = computed(() => (sideTheme.value === 'theme-dark' ? variables.menuColor : variables.menuLightColor));
</script>
src/layout/components/SocialCallback/index.vue
@@ -10,7 +10,6 @@
const route = useRoute();
const loading = ref(true);
/**
 * æŽ¥æ”¶Route传递的参数
 * @param {Object} route.query.
@@ -18,8 +17,7 @@
const code = route.query.code as string;
const state = route.query.state as string;
const source = route.query.source as string;
const tenantId = localStorage.getItem("tenantId") ? localStorage.getItem("tenantId") as string : '000000';
const tenantId = localStorage.getItem('tenantId') ? (localStorage.getItem('tenantId') as string) : '000000';
const processResponse = async (res: any) => {
  if (res.code !== 200) {
@@ -52,7 +50,6 @@
};
const loginByCode = async (data: LoginData) => {
  console.log(2)
  try {
    const res = await login(data);
    await processResponse(res);
src/layout/components/TagsView/ScrollPane.vue
@@ -5,84 +5,84 @@
</template>
<script setup lang="ts">
import useTagsViewStore from '@/store/modules/tagsView'
import { TagView } from 'vue-router'
import useTagsViewStore from '@/store/modules/tagsView';
import { TagView } from 'vue-router';
const tagAndTagSpacing = ref(4);
const scrollContainerRef = ref<ElScrollbarInstance>()
const scrollContainerRef = ref<ElScrollbarInstance>();
const scrollWrapper = computed(() => scrollContainerRef.value?.$refs.wrapRef as any);
onMounted(() => {
    scrollWrapper.value?.addEventListener('scroll', emitScroll, true)
})
  scrollWrapper.value?.addEventListener('scroll', emitScroll, true);
});
onBeforeUnmount(() => {
    scrollWrapper.value?.removeEventListener('scroll', emitScroll)
})
  scrollWrapper.value?.removeEventListener('scroll', emitScroll);
});
const handleScroll = (e: WheelEvent) => {
    const eventDelta = (e as any).wheelDelta || - e.deltaY * 40
    const $scrollWrapper = scrollWrapper.value;
    $scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
}
const emits = defineEmits(['scroll'])
  const eventDelta = (e as any).wheelDelta || -e.deltaY * 40;
  const $scrollWrapper = scrollWrapper.value;
  $scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4;
};
const emits = defineEmits(['scroll']);
const emitScroll = () => {
    emits('scroll')
}
  emits('scroll');
};
const tagsViewStore = useTagsViewStore()
const tagsViewStore = useTagsViewStore();
const visitedViews = computed(() => tagsViewStore.visitedViews);
const moveToTarget = (currentTag: TagView) => {
    const $container = scrollContainerRef.value?.$el
    const $containerWidth = $container.offsetWidth
    const $scrollWrapper = scrollWrapper.value;
  const $container = scrollContainerRef.value?.$el;
  const $containerWidth = $container.offsetWidth;
  const $scrollWrapper = scrollWrapper.value;
    let firstTag = null
    let lastTag = null
  let firstTag = null;
  let lastTag = null;
    // find first tag and last tag
    if (visitedViews.value.length > 0) {
        firstTag = visitedViews.value[0]
        lastTag = visitedViews.value[visitedViews.value.length - 1]
  // find first tag and last tag
  if (visitedViews.value.length > 0) {
    firstTag = visitedViews.value[0];
    lastTag = visitedViews.value[visitedViews.value.length - 1];
  }
  if (firstTag === currentTag) {
    $scrollWrapper.scrollLeft = 0;
  } else if (lastTag === currentTag) {
    $scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth;
  } else {
    const tagListDom: any = document.getElementsByClassName('tags-view-item');
    const currentIndex = visitedViews.value.findIndex((item) => item === currentTag);
    let prevTag = null;
    let nextTag = null;
    for (const k in tagListDom) {
      if (k !== 'length' && Object.hasOwnProperty.call(tagListDom, k)) {
        if (tagListDom[k].dataset.path === visitedViews.value[currentIndex - 1].path) {
          prevTag = tagListDom[k];
        }
        if (tagListDom[k].dataset.path === visitedViews.value[currentIndex + 1].path) {
          nextTag = tagListDom[k];
        }
      }
    }
    if (firstTag === currentTag) {
        $scrollWrapper.scrollLeft = 0
    } else if (lastTag === currentTag) {
        $scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
    } else {
        const tagListDom: any = document.getElementsByClassName('tags-view-item');
        const currentIndex = visitedViews.value.findIndex(item => item === currentTag)
        let prevTag = null
        let nextTag = null
    // the tag's offsetLeft after of nextTag
    const afterNextTagOffsetLeft = nextTag.offsetLeft + nextTag.offsetWidth + tagAndTagSpacing.value;
        for (const k in tagListDom) {
            if (k !== 'length' && Object.hasOwnProperty.call(tagListDom, k)) {
                if (tagListDom[k].dataset.path === visitedViews.value[currentIndex - 1].path) {
                    prevTag = tagListDom[k];
                }
                if (tagListDom[k].dataset.path === visitedViews.value[currentIndex + 1].path) {
                    nextTag = tagListDom[k];
                }
            }
        }
        // the tag's offsetLeft after of nextTag
        const afterNextTagOffsetLeft = nextTag.offsetLeft + nextTag.offsetWidth + tagAndTagSpacing.value
        // the tag's offsetLeft before of prevTag
        const beforePrevTagOffsetLeft = prevTag.offsetLeft - tagAndTagSpacing.value
        if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
            $scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
        } else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
            $scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
        }
    // the tag's offsetLeft before of prevTag
    const beforePrevTagOffsetLeft = prevTag.offsetLeft - tagAndTagSpacing.value;
    if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
      $scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth;
    } else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
      $scrollWrapper.scrollLeft = beforePrevTagOffsetLeft;
    }
}
  }
};
defineExpose({
    moveToTarget,
})
  moveToTarget
});
</script>
<style lang="scss" scoped>
src/layout/components/TagsView/index.vue
@@ -14,36 +14,35 @@
      >
        {{ tag.title }}
        <span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
          <close class="el-icon-close" style="width: 1em; height: 1em;vertical-align: middle;" />
          <close class="el-icon-close" style="width: 1em; height: 1em; vertical-align: middle" />
        </span>
      </router-link>
    </scroll-pane>
    <ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
      <li @click="refreshSelectedTag(selectedTag)"><refresh-right style="width: 1em; height: 1em;" /> åˆ·æ–°é¡µé¢</li>
      <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)"><close style="width: 1em; height: 1em;" /> å…³é—­å½“前</li>
      <li @click="closeOthersTags"><circle-close style="width: 1em; height: 1em;" /> å…³é—­å…¶ä»–</li>
      <li v-if="!isFirstView()" @click="closeLeftTags"><back style="width: 1em; height: 1em;" /> å…³é—­å·¦ä¾§</li>
      <li v-if="!isLastView()" @click="closeRightTags"><right style="width: 1em; height: 1em;" /> å…³é—­å³ä¾§</li>
      <li @click="closeAllTags(selectedTag)"><circle-close style="width: 1em; height: 1em;" /> å…¨éƒ¨å…³é—­</li>
      <li @click="refreshSelectedTag(selectedTag)"><refresh-right style="width: 1em; height: 1em" /> åˆ·æ–°é¡µé¢</li>
      <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)"><close style="width: 1em; height: 1em" /> å…³é—­å½“前</li>
      <li @click="closeOthersTags"><circle-close style="width: 1em; height: 1em" /> å…³é—­å…¶ä»–</li>
      <li v-if="!isFirstView()" @click="closeLeftTags"><back style="width: 1em; height: 1em" /> å…³é—­å·¦ä¾§</li>
      <li v-if="!isLastView()" @click="closeRightTags"><right style="width: 1em; height: 1em" /> å…³é—­å³ä¾§</li>
      <li @click="closeAllTags(selectedTag)"><circle-close style="width: 1em; height: 1em" /> å…¨éƒ¨å…³é—­</li>
    </ul>
  </div>
</template>
<script setup lang="ts">
import ScrollPane from './ScrollPane.vue'
import { getNormalPath } from '@/utils/ruoyi'
import useTagsViewStore from "@/store/modules/tagsView";
import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission'
import { ComponentInternalInstance } from "vue";
import { RouteOption, TagView, RouteLocationRaw } from "vue-router";
import ScrollPane from './ScrollPane.vue';
import { getNormalPath } from '@/utils/ruoyi';
import useTagsViewStore from '@/store/modules/tagsView';
import useSettingsStore from '@/store/modules/settings';
import usePermissionStore from '@/store/modules/permission';
import { RouteRecordRaw, TagView } from 'vue-router';
const visible = ref(false);
const top = ref(0);
const left = ref(0);
const selectedTag = ref<TagView>({});
const affixTags = ref<TagView[]>([]);
const scrollPaneRef = ref(ScrollPane);
const scrollPaneRef = ref<InstanceType<typeof ScrollPane>>();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const route = useRoute();
@@ -54,186 +53,186 @@
const theme = computed(() => useSettingsStore().theme);
watch(route, () => {
    addTags();
    moveToCurrentTag();
})
  addTags();
  moveToCurrentTag();
});
watch(visible, (value) => {
    if (value) {
        document.body.addEventListener('click', closeMenu);
    } else {
        document.body.removeEventListener('click', closeMenu);
    }
})
  if (value) {
    document.body.addEventListener('click', closeMenu);
  } else {
    document.body.removeEventListener('click', closeMenu);
  }
});
const isActive = (r: TagView): boolean => {
    return r.path === route.path;
}
  return r.path === route.path;
};
const activeStyle = (tag: TagView) => {
    if (!isActive(tag)) return {};
    return {
        "background-color": theme.value,
        "border-color": theme.value
    };
}
  if (!isActive(tag)) return {};
  return {
    'background-color': theme.value,
    'border-color': theme.value
  };
};
const isAffix = (tag: TagView) => {
    return tag.meta && tag.meta.affix;
}
  return tag.meta && tag.meta.affix;
};
const isFirstView = () => {
    try {
        return selectedTag.value.fullPath === '/index' || selectedTag.value.fullPath === visitedViews.value[1].fullPath;
    } catch (err) {
        return false;
    }
}
  try {
    return selectedTag.value.fullPath === '/index' || selectedTag.value.fullPath === visitedViews.value[1].fullPath;
  } catch (err) {
    return false;
  }
};
const isLastView = () => {
    try {
        return selectedTag.value.fullPath === visitedViews.value[visitedViews.value.length - 1].fullPath;
    } catch (err) {
        return false;
  try {
    return selectedTag.value.fullPath === visitedViews.value[visitedViews.value.length - 1].fullPath;
  } catch (err) {
    return false;
  }
};
const filterAffixTags = (routes: RouteRecordRaw[], basePath = '') => {
  let tags: TagView[] = [];
  routes.forEach((route) => {
    if (route.meta && route.meta.affix) {
      const tagPath = getNormalPath(basePath + '/' + route.path);
      tags.push({
        fullPath: tagPath,
        path: tagPath,
        name: route.name as string,
        meta: { ...route.meta }
      });
    }
}
const filterAffixTags = (routes:RouteOption [], basePath = '') => {
    let tags:TagView[] = []
    routes.forEach(route => {
        if (route.meta && route.meta.affix) {
            const tagPath = getNormalPath(basePath + '/' + route.path);
            tags.push({
                fullPath: tagPath,
                path: tagPath,
                name: route.name,
                meta: { ...route.meta }
            })
        }
        if (route.children) {
            const tempTags = filterAffixTags(route.children, route.path);
            if (tempTags.length >= 1) {
                tags = [...tags, ...tempTags];
            }
        }
    })
    return tags
}
    if (route.children) {
      const tempTags = filterAffixTags(route.children, route.path);
      if (tempTags.length >= 1) {
        tags = [...tags, ...tempTags];
      }
    }
  });
  return tags;
};
const initTags = () => {
    const res = filterAffixTags(routes.value);
    affixTags.value = res;
    for (const tag of res) {
        // Must have tag name
        if (tag.name) {
            useTagsViewStore().addVisitedView(tag);
        }
  const res = filterAffixTags(routes.value as any);
  affixTags.value = res;
  for (const tag of res) {
    // Must have tag name
    if (tag.name) {
      useTagsViewStore().addVisitedView(tag);
    }
}
  }
};
const addTags = () => {
    const { name } = route;
    if(route.query.title) {
        route.meta.title = route.query.title;
    }
    if (name) {
        useTagsViewStore().addView(route);
        if (route.meta.link) {
            useTagsViewStore().addIframeView(route);
        }
    }
    return false
}
const moveToCurrentTag = () => {
    nextTick(() => {
        for (const r of visitedViews.value) {
            if (r.path === route.path) {
                scrollPaneRef.value.moveToTarget(r);
                // when query is different then update
                if (r.fullPath !== route.fullPath) {
                    useTagsViewStore().updateVisitedView(route);
                }
            }
        }
    })
}
const refreshSelectedTag = (view: TagView) => {
    proxy?.$tab.refreshPage(view);
  const { name } = route;
  if (route.query.title) {
    route.meta.title = route.query.title as string;
  }
  if (name) {
    useTagsViewStore().addView(route as any);
    if (route.meta.link) {
        useTagsViewStore().delIframeView(route);
      useTagsViewStore().addIframeView(route as any);
    }
}
  }
  return false;
};
const moveToCurrentTag = () => {
  nextTick(() => {
    for (const r of visitedViews.value) {
      if (r.path === route.path) {
        scrollPaneRef.value?.moveToTarget(r);
        // when query is different then update
        if (r.fullPath !== route.fullPath) {
          useTagsViewStore().updateVisitedView(route);
        }
      }
    }
  });
};
const refreshSelectedTag = (view: TagView) => {
  proxy?.$tab.refreshPage(view);
  if (route.meta.link) {
    useTagsViewStore().delIframeView(route as any);
  }
};
const closeSelectedTag = (view: TagView) => {
    proxy?.$tab.closePage(view).then(({ visitedViews }: any) => {
        if (isActive(view)) {
            toLastView(visitedViews, view);
        }
    })
}
  proxy?.$tab.closePage(view).then(({ visitedViews }: any) => {
    if (isActive(view)) {
      toLastView(visitedViews, view);
    }
  });
};
const closeRightTags = () => {
    proxy?.$tab.closeRightPage(selectedTag.value).then(visitedViews => {
        if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
            toLastView(visitedViews);
        }
    })
}
  proxy?.$tab.closeRightPage(selectedTag.value).then((visitedViews) => {
    if (!visitedViews.find((i) => i.fullPath === route.fullPath)) {
      toLastView(visitedViews);
    }
  });
};
const closeLeftTags = () => {
    proxy?.$tab.closeLeftPage(selectedTag.value).then(visitedViews => {
        if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
            toLastView(visitedViews);
        }
    })
}
  proxy?.$tab.closeLeftPage(selectedTag.value).then((visitedViews) => {
    if (!visitedViews.find((i: TagView) => i.fullPath === route.fullPath)) {
      toLastView(visitedViews);
    }
  });
};
const closeOthersTags = () => {
    router.push(selectedTag.value as RouteLocationRaw).catch(() => { });
    proxy?.$tab.closeOtherPage(selectedTag.value).then(() => {
        moveToCurrentTag();
    })
}
  router.push(selectedTag.value).catch(() => {});
  proxy?.$tab.closeOtherPage(selectedTag.value).then(() => {
    moveToCurrentTag();
  });
};
const closeAllTags = (view: TagView) => {
    proxy?.$tab.closeAllPage().then(({ visitedViews }) => {
        if (affixTags.value.some(tag => tag.path === route.path)) {
            return;
        }
        toLastView(visitedViews, view);
    })
}
const toLastView = (visitedViews:TagView[], view?: TagView) => {
    const latestView = visitedViews.slice(-1)[0];
    if (latestView) {
        router.push(latestView.fullPath as string);
    } else {
        // now the default is to redirect to the home page if there is no tags-view,
        // you can adjust it according to your needs.
        if (view?.name === 'Dashboard') {
            // to reload home page
            router.replace({ path: '/redirect' + view?.fullPath });
        } else {
            router.push('/');
        }
  proxy?.$tab.closeAllPage().then(({ visitedViews }) => {
    if (affixTags.value.some((tag) => tag.path === route.path)) {
      return;
    }
}
    toLastView(visitedViews, view);
  });
};
const toLastView = (visitedViews: TagView[], view?: TagView) => {
  const latestView = visitedViews.slice(-1)[0];
  if (latestView) {
    router.push(latestView.fullPath as string);
  } else {
    // now the default is to redirect to the home page if there is no tags-view,
    // you can adjust it according to your needs.
    if (view?.name === 'Dashboard') {
      // to reload home page
      router.replace({ path: '/redirect' + view?.fullPath });
    } else {
      router.push('/');
    }
  }
};
const openMenu = (tag: TagView, e: MouseEvent) => {
    const menuMinWidth = 105;
    const offsetLeft = proxy?.$el.getBoundingClientRect().left; // container margin left
    const offsetWidth = proxy?.$el.offsetWidth; // container width
    const maxLeft = offsetWidth - menuMinWidth; // left boundary
    const l = e.clientX - offsetLeft + 15; // 15: margin right
  const menuMinWidth = 105;
  const offsetLeft = proxy?.$el.getBoundingClientRect().left; // container margin left
  const offsetWidth = proxy?.$el.offsetWidth; // container width
  const maxLeft = offsetWidth - menuMinWidth; // left boundary
  const l = e.clientX - offsetLeft + 15; // 15: margin right
    if (l > maxLeft) {
        left.value = maxLeft;
    } else {
        left.value = l;
    }
  if (l > maxLeft) {
    left.value = maxLeft;
  } else {
    left.value = l;
  }
    top.value = e.clientY
    visible.value = true;
    selectedTag.value = tag;
}
  top.value = e.clientY;
  visible.value = true;
  selectedTag.value = tag;
};
const closeMenu = () => {
    visible.value = false;
}
  visible.value = false;
};
const handleScroll = () => {
    closeMenu();
}
  closeMenu();
};
onMounted(() => {
    initTags();
    addTags();
})
  initTags();
  addTags();
});
</script>
<style lang="scss" scoped>
@@ -242,7 +241,9 @@
  width: 100%;
  background-color: var(--el-bg-color);
  border: 1px solid var(--el-border-color-light);
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);
  box-shadow:
    0 1px 3px 0 rgba(0, 0, 0, 0.12),
    0 0 3px 0 rgba(0, 0, 0, 0.04);
  .tags-view-wrapper {
    .tags-view-item {
      display: inline-block;
@@ -271,7 +272,7 @@
        color: #fff;
        border-color: #42b983;
        &::before {
          content: "";
          content: '';
          background: #fff;
          display: inline-block;
          width: 8px;
src/layout/components/TopBar/search.vue
@@ -3,12 +3,12 @@
    <el-dialog v-model="state.isShowSearch" destroy-on-close :show-close="false">
      <template #footer>
        <el-autocomplete
          ref="layoutMenuAutocompleteRef"
          v-model="state.menuQuery"
          :fetch-suggestions="menuSearch"
          placeholder="搜索"
          ref="layoutMenuAutocompleteRef"
          @select="onHandleSelect"
          :fit-input-width="true"
          @select="onHandleSelect"
        >
          <template #prefix>
            <svg-icon class-name="search-icon" icon-class="search" />
@@ -29,130 +29,129 @@
import { getNormalPath } from '@/utils/ruoyi';
import { isHttp } from '@/utils/validate';
import usePermissionStore from '@/store/modules/permission';
import { RouteOption } from 'vue-router';
import { RouteRecordRaw } from 'vue-router';
type Router = Array<{
    path: string;
    icon: string;
    title: string[];
}>
  path: string;
  icon: string;
  title: string[];
}>;
type SearchState<T = any> = {
    isShowSearch: boolean;
    menuQuery: string;
    menuList: T[];
  isShowSearch: boolean;
  menuQuery: string;
  menuList: T[];
};
// å®šä¹‰å˜é‡å†…容
const layoutMenuAutocompleteRef = ref();
const router = useRouter();
const routes = computed(() => usePermissionStore().routes);
const state = reactive<SearchState>({
    isShowSearch: false,
    menuQuery: '',
    menuList: [],
  isShowSearch: false,
  menuQuery: '',
  menuList: []
});
// æœç´¢å¼¹çª—打开
const openSearch = () => {
    state.menuQuery = '';
    state.isShowSearch = true;
    state.menuList = generateRoutes(routes.value);
    nextTick(() => {
        setTimeout(() => {
            layoutMenuAutocompleteRef.value.focus();
        });
    });
  state.menuQuery = '';
  state.isShowSearch = true;
  state.menuList = generateRoutes(routes.value);
  nextTick(() => {
    setTimeout(() => {
      layoutMenuAutocompleteRef.value.focus();
    });
  });
};
// æœç´¢å¼¹çª—关闭
const closeSearch = () => {
    state.isShowSearch = false;
  state.isShowSearch = false;
};
// èœå•搜索数据过滤
const menuSearch = (queryString: string, cb: Function) => {
    let options = state.menuList.filter((item) => {
        return item.title.indexOf(queryString) > -1;
    });
    cb(options);
  let options = state.menuList.filter((item) => {
    return item.title.indexOf(queryString) > -1;
  });
  cb(options);
};
// Filter out the routes that can be displayed in the sidebar
// And generate the internationalized title
const generateRoutes = (routes: RouteOption[], basePath = '', prefixTitle: string[] = []) => {
    let res: Router = []
    routes.forEach(r => {
        // skip hidden router
        if (!r.hidden) {
            const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
                const data: any = {
                    path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
                    icon: r.meta?.icon,
                    title: [...prefixTitle]
                }
                if (r.meta && r.meta.title) {
                    data.title = [...data.title, r.meta.title];
                    if (r.redirect !== 'noRedirect') {
                        // only push the routes with title
                        // special case: need to exclude parent router without redirect
            res.push(data);
                    }
                }
                // recursive child routes
                if (r.children) {
                        const tempRoutes = generateRoutes(r.children, data.path, data.title);
                        if (tempRoutes.length >= 1) {
                                res = [...res, ...tempRoutes];
                        }
                }
        }
    })
    res.forEach((item: any) => {
        if (item.title instanceof Array) {
            item.title = item.title.join('/');
        }
    });
    return res;
}
const generateRoutes = (routes: RouteRecordRaw[], basePath = '', prefixTitle: string[] = []) => {
  let res: Router = [];
  routes.forEach((r) => {
    // skip hidden router
    if (!r.hidden) {
      const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
      const data: any = {
        path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
        icon: r.meta?.icon,
        title: [...prefixTitle]
      };
      if (r.meta && r.meta.title) {
        data.title = [...data.title, r.meta.title];
        if (r.redirect !== 'noRedirect') {
          // only push the routes with title
          // special case: need to exclude parent router without redirect
          res.push(data);
        }
      }
      // recursive child routes
      if (r.children) {
        const tempRoutes = generateRoutes(r.children, data.path, data.title);
        if (tempRoutes.length >= 1) {
          res = [...res, ...tempRoutes];
        }
      }
    }
  });
  res.forEach((item: any) => {
    if (item.title instanceof Array) {
      item.title = item.title.join('/');
    }
  });
  return res;
};
// å½“前菜单选中时
const onHandleSelect = (val: any) => {
    const paths = val.path;
    if (isHttp(paths)) {
        // http(s):// è·¯å¾„新窗口打开
        const pindex = paths.indexOf("http");
        window.open(paths.substring(pindex, paths.length), "_blank");
    } else {
        router.push(paths);
    }
    state.menuQuery = ''
    closeSearch();
  const paths = val.path;
  if (isHttp(paths)) {
    // http(s):// è·¯å¾„新窗口打开
    const pindex = paths.indexOf('http');
    window.open(paths.substring(pindex, paths.length), '_blank');
  } else {
    router.push(paths);
  }
  state.menuQuery = '';
  closeSearch();
};
// æš´éœ²å˜é‡
defineExpose({
    openSearch
  openSearch
});
</script>
<style scoped lang="scss">
.layout-search-dialog {
    position: relative;
    :deep(.el-dialog) {
        .el-dialog__header,
        .el-dialog__body {
            display: none;
        }
        .el-dialog__footer {
            width: 100%;
            position: absolute;
            left: 50%;
            transform: translateX(-50%);
            top: -53vh;
        }
    }
    :deep(.el-autocomplete) {
        width: 560px;
        position: absolute;
        top: 150px;
        left: 50%;
        transform: translateX(-50%);
    }
  position: relative;
  :deep(.el-dialog) {
    .el-dialog__header,
    .el-dialog__body {
      display: none;
    }
    .el-dialog__footer {
      width: 100%;
      position: absolute;
      left: 50%;
      transform: translateX(-50%);
      top: -53vh;
    }
  }
  :deep(.el-autocomplete) {
    width: 560px;
    position: absolute;
    top: 150px;
    left: 50%;
    transform: translateX(-50%);
  }
}
</style>
src/layout/components/notice/index.vue
@@ -1,12 +1,12 @@
<template>
  <div class="layout-navbars-breadcrumb-user-news" v-loading="state.loading">
  <div v-loading="state.loading" class="layout-navbars-breadcrumb-user-news">
    <div class="head-box">
      <div class="head-box-title">通知公告</div>
      <div class="head-box-btn" @click="readAll">全部已读</div>
    </div>
    <div class="content-box" v-loading="state.loading">
    <div v-loading="state.loading" class="content-box">
      <template v-if="newsList.length > 0">
        <div class="content-box-item" v-for="(v, k) in newsList" :key="k" @click="onNewsClick(k)">
        <div v-for="(v, k) in newsList" :key="k" class="content-box-item" @click="onNewsClick(k)">
          <div class="item-conten">
            <div>{{ v.message }}</div>
            <div class="content-box-msg"></div>
@@ -17,26 +17,24 @@
          <span v-else class="el-tag el-tag--danger el-tag--mini read">未读</span>
        </div>
      </template>
      <el-empty :description="'消息为空'" v-else></el-empty>
      <el-empty v-else :description="'消息为空'"></el-empty>
    </div>
    <div class="foot-box" @click="onGoToGiteeClick" v-if="newsList.length > 0">前往gitee</div>
    <div v-if="newsList.length > 0" class="foot-box" @click="onGoToGiteeClick">前往gitee</div>
  </div>
</template>
<script setup lang="ts" name="layoutBreadcrumbUserNews">
import { ref } from "vue";
import { storeToRefs } from 'pinia'
import { nextTick, onMounted, reactive } from "vue";
import { storeToRefs } from 'pinia';
import useNoticeStore from '@/store/modules/notice';
const noticeStore = storeToRefs(useNoticeStore());
const {readAll} = useNoticeStore();
const { readAll } = useNoticeStore();
// å®šä¹‰å˜é‡å†…容
const state = reactive({
  loading: false,
  loading: false
});
const newsList =ref([]) as any;
const newsList = ref([]) as any;
/**
 * åˆå§‹åŒ–数据
@@ -48,7 +46,6 @@
  state.loading = false;
};
//点击消息,写入已读
const onNewsClick = (item: any) => {
  newsList.value[item].read = true;
@@ -58,7 +55,7 @@
// å‰å¾€é€šçŸ¥ä¸­å¿ƒç‚¹å‡»
const onGoToGiteeClick = () => {
  window.open("https://gitee.com/dromara/RuoYi-Vue-Plus/tree/5.X/");
  window.open('https://gitee.com/dromara/RuoYi-Vue-Plus/tree/5.X/');
};
onMounted(() => {
src/layout/index.vue
@@ -12,7 +12,7 @@
        <settings ref="settingRef" />
      </el-scrollbar> -->
      <div :class="{ 'fixed-header': fixedHeader }">
        <navbar ref="navbarRef" @setLayout="setLayout" />
        <navbar ref="navbarRef" @set-layout="setLayout" />
        <tags-view v-if="needTagsView" />
      </div>
      <app-main />
@@ -22,12 +22,12 @@
</template>
<script setup lang="ts">
import SideBar from './components/Sidebar/index.vue'
import { AppMain, Navbar, Settings, TagsView } from './components'
import useAppStore from '@/store/modules/app'
import useSettingsStore from '@/store/modules/settings'
import SideBar from './components/Sidebar/index.vue';
import { AppMain, Navbar, Settings, TagsView } from './components';
import useAppStore from '@/store/modules/app';
import useSettingsStore from '@/store/modules/settings';
const settingsStore = useSettingsStore()
const settingsStore = useSettingsStore();
const theme = computed(() => settingsStore.theme);
const sidebar = computed(() => useAppStore().sidebar);
const device = computed(() => useAppStore().device);
@@ -35,48 +35,48 @@
const fixedHeader = computed(() => settingsStore.fixedHeader);
const classObj = computed(() => ({
    hideSidebar: !sidebar.value.opened,
    openSidebar: sidebar.value.opened,
    withoutAnimation: sidebar.value.withoutAnimation,
    mobile: device.value === 'mobile'
}))
  hideSidebar: !sidebar.value.opened,
  openSidebar: sidebar.value.opened,
  withoutAnimation: sidebar.value.withoutAnimation,
  mobile: device.value === 'mobile'
}));
const { width } = useWindowSize();
const WIDTH = 992; // refer to Bootstrap's responsive design
watchEffect(() => {
    if (device.value === 'mobile' && sidebar.value.opened) {
        useAppStore().closeSideBar({ withoutAnimation: false })
    }
    if (width.value - 1 < WIDTH) {
        useAppStore().toggleDevice('mobile')
        useAppStore().closeSideBar({ withoutAnimation: true })
    } else {
        useAppStore().toggleDevice('desktop')
    }
})
  if (device.value === 'mobile' && sidebar.value.opened) {
    useAppStore().closeSideBar({ withoutAnimation: false });
  }
  if (width.value - 1 < WIDTH) {
    useAppStore().toggleDevice('mobile');
    useAppStore().closeSideBar({ withoutAnimation: true });
  } else {
    useAppStore().toggleDevice('desktop');
  }
});
const navbarRef = ref(Navbar);
const settingRef = ref(Settings);
const navbarRef = ref<InstanceType<typeof Navbar>>();
const settingRef = ref<InstanceType<typeof Settings>>();
onMounted(() => {
  nextTick(() => {
    navbarRef.value.initTenantList();
  })
})
    navbarRef.value?.initTenantList();
  });
});
const handleClickOutside = () => {
  useAppStore().closeSideBar({ withoutAnimation: false })
}
  useAppStore().closeSideBar({ withoutAnimation: false });
};
const setLayout = () => {
  settingRef.value.openSetting();
}
  settingRef.value?.openSetting();
};
</script>
<style lang="scss" scoped>
  @import "@/assets/styles/mixin.scss";
  @import "@/assets/styles/variables.module.scss";
@import '@/assets/styles/mixin.scss';
@import '@/assets/styles/variables.module.scss';
.app-wrapper {
  @include clearfix;
src/permission.ts
@@ -8,6 +8,7 @@
import useUserStore from '@/store/modules/user';
import useSettingsStore from '@/store/modules/settings';
import usePermissionStore from '@/store/modules/permission';
import { RouteRecordRaw } from 'vue-router';
NProgress.configure({ showSpinner: false });
const whiteList = ['/login', '/register', '/social-callback'];
@@ -35,7 +36,7 @@
          isRelogin.show = false;
          const accessRoutes = await usePermissionStore().generateRoutes();
          // æ ¹æ®roles权限生成可访问的路由表
          accessRoutes.forEach((route) => {
          accessRoutes.forEach((route: RouteRecordRaw) => {
            if (!isHttp(route.path)) {
              router.addRoute(route); // åŠ¨æ€æ·»åŠ å¯è®¿é—®è·¯ç”±è¡¨
            }
@@ -48,7 +49,7 @@
    }
  } else {
    // æ²¡æœ‰token
    if (whiteList.indexOf(to.path) !== -1) {
    if (whiteList.indexOf(to.path as string) !== -1) {
      // åœ¨å…ç™»å½•白名单,直接进入
      next();
    } else {
src/plugins/tab.ts
@@ -1,6 +1,6 @@
import { useTagsViewStore } from '@/store/modules/tagsView';
import router from '@/router';
import { TagView, RouteLocationRaw } from 'vue-router';
import { TagView, RouteLocationMatched } from 'vue-router';
export default {
  /**
@@ -10,7 +10,7 @@
  async refreshPage(obj?: TagView): Promise<void> {
    const { path, query, matched } = router.currentRoute.value;
    if (obj === undefined) {
      matched.forEach((m) => {
      matched.forEach((m: RouteLocationMatched) => {
        if (m.components && m.components.default && m.components.default.name) {
          if (!['Layout', 'ParentView'].includes(m.components.default.name)) {
            obj = { name: m.components.default.name, path: path, query: query };
@@ -31,8 +31,8 @@
    });
  },
  // å…³é—­å½“前tab页签,打开新页签
  closeOpenPage(obj: RouteLocationRaw): void {
    useTagsViewStore().delView(router.currentRoute.value);
  closeOpenPage(obj: TagView): void {
    useTagsViewStore().delView(router.currentRoute.value as any);
    if (obj !== undefined) {
      router.push(obj);
    }
@@ -41,10 +41,10 @@
  async closePage(obj?: TagView): Promise<{ visitedViews: TagView[]; cachedViews: string[] } | any> {
    if (obj === undefined) {
      // prettier-ignore
      const { visitedViews } = await useTagsViewStore().delView(router.currentRoute.value) as any
      const { visitedViews } = await useTagsViewStore().delView(router.currentRoute.value as any)
      const latestView = visitedViews.slice(-1)[0];
      if (latestView) {
        return router.push(latestView.fullPath);
        return router.push(latestView.fullPath as any);
      }
      return router.push('/');
    }
@@ -56,15 +56,15 @@
  },
  // å…³é—­å·¦ä¾§tab页签
  closeLeftPage(obj?: TagView) {
    return useTagsViewStore().delLeftTags(obj || router.currentRoute.value);
    return useTagsViewStore().delLeftTags(obj || (router.currentRoute.value as any));
  },
  // å…³é—­å³ä¾§tab页签
  closeRightPage(obj?: TagView) {
    return useTagsViewStore().delRightTags(obj || router.currentRoute.value);
    return useTagsViewStore().delRightTags(obj || (router.currentRoute.value as any));
  },
  // å…³é—­å…¶ä»–tab页签
  closeOtherPage(obj?: TagView) {
    return useTagsViewStore().delOthersViews(obj || router.currentRoute.value);
    return useTagsViewStore().delOthersViews(obj || (router.currentRoute.value as any));
  },
  /**
   * æ‰“å¼€tab页签
src/router/index.ts
@@ -1,4 +1,4 @@
import { createWebHistory, createRouter, RouteOption } from 'vue-router';
import { createWebHistory, createRouter, RouteRecordRaw } from 'vue-router';
/* Layout */
import Layout from '@/layout/index.vue';
@@ -25,7 +25,7 @@
 */
// å…¬å…±è·¯ç”±
export const constantRoutes: RouteOption[] = [
export const constantRoutes: RouteRecordRaw[] = [
  {
    path: '/redirect',
    component: Layout,
@@ -92,7 +92,7 @@
];
// åŠ¨æ€è·¯ç”±ï¼ŒåŸºäºŽç”¨æˆ·æƒé™åŠ¨æ€åŽ»åŠ è½½
export const dynamicRoutes: RouteOption[] = [
export const dynamicRoutes: RouteRecordRaw[] = [
  {
    path: '/system/user-auth',
    component: Layout,
src/store/modules/notice.ts
@@ -22,7 +22,7 @@
  //实现全部已读
  const readAll = () => {
    state.notices.forEach((item) => {
    state.notices.forEach((item: any) => {
      item.read = true;
    });
  };
src/store/modules/permission.ts
@@ -2,35 +2,36 @@
import router, { constantRoutes, dynamicRoutes } from '@/router';
import store from '@/store';
import { getRouters } from '@/api/menu';
import auth from '@/plugins/auth';
import { RouteRecordRaw } from 'vue-router';
import Layout from '@/layout/index.vue';
import ParentView from '@/components/ParentView/index.vue';
import InnerLink from '@/layout/components/InnerLink/index.vue';
import auth from '@/plugins/auth';
import { RouteOption } from 'vue-router';
// åŒ¹é…views里面所有的.vue文件
const modules = import.meta.glob('./../../views/**/*.vue');
export const usePermissionStore = defineStore('permission', () => {
  const routes = ref<RouteOption[]>([]);
  const addRoutes = ref<RouteOption[]>([]);
  const defaultRoutes = ref<RouteOption[]>([]);
  const topbarRouters = ref<RouteOption[]>([]);
  const sidebarRouters = ref<RouteOption[]>([]);
  const routes = ref<RouteRecordRaw[]>([]);
  const addRoutes = ref<RouteRecordRaw[]>([]);
  const defaultRoutes = ref<RouteRecordRaw[]>([]);
  const topbarRouters = ref<RouteRecordRaw[]>([]);
  const sidebarRouters = ref<RouteRecordRaw[]>([]);
  const setRoutes = (newRoutes: RouteOption[]): void => {
  const setRoutes = (newRoutes: RouteRecordRaw[]): void => {
    addRoutes.value = newRoutes;
    routes.value = constantRoutes.concat(newRoutes);
  };
  const setDefaultRoutes = (routes: RouteOption[]): void => {
  const setDefaultRoutes = (routes: RouteRecordRaw[]): void => {
    defaultRoutes.value = constantRoutes.concat(routes);
  };
  const setTopbarRoutes = (routes: RouteOption[]): void => {
  const setTopbarRoutes = (routes: RouteRecordRaw[]): void => {
    topbarRouters.value = routes;
  };
  const setSidebarRouters = (routes: RouteOption[]): void => {
  const setSidebarRouters = (routes: RouteRecordRaw[]): void => {
    sidebarRouters.value = routes;
  };
  const generateRoutes = async (): Promise<RouteOption[]> => {
  const generateRoutes = async (): Promise<RouteRecordRaw[]> => {
    const res = await getRouters();
    const { data } = res;
    const sdata = JSON.parse(JSON.stringify(data));
@@ -47,7 +48,7 @@
    setSidebarRouters(constantRoutes.concat(sidebarRoutes));
    setDefaultRoutes(sidebarRoutes);
    setTopbarRoutes(defaultRoutes);
    return new Promise<RouteOption[]>((resolve) => resolve(rewriteRoutes));
    return new Promise<RouteRecordRaw[]>((resolve) => resolve(rewriteRoutes));
  };
  /**
@@ -56,22 +57,20 @@
   * @param lastRouter ä¸Šä¸€çº§è·¯ç”±
   * @param type æ˜¯å¦æ˜¯é‡å†™è·¯ç”±
   */
  const filterAsyncRouter = (asyncRouterMap: RouteOption[], lastRouter?: RouteOption, type = false): RouteOption[] => {
  const filterAsyncRouter = (asyncRouterMap: RouteRecordRaw[], lastRouter?: RouteRecordRaw, type = false): RouteRecordRaw[] => {
    return asyncRouterMap.filter((route) => {
      if (type && route.children) {
        route.children = filterChildren(route.children, undefined);
      }
      if (route.component) {
        // Layout ParentView ç»„件特殊处理
        if (route.component === 'Layout') {
          route.component = Layout;
        } else if (route.component === 'ParentView') {
          route.component = ParentView;
        } else if (route.component === 'InnerLink') {
          route.component = InnerLink;
        } else {
          route.component = loadView(route.component);
        }
      // Layout ParentView ç»„件特殊处理
      if (route.component?.toString() === 'Layout') {
        route.component = Layout;
      } else if (route.component?.toString() === 'ParentView') {
        route.component = ParentView;
      } else if (route.component?.toString() === 'InnerLink') {
        route.component = InnerLink;
      } else {
        route.component = loadView(route.component);
      }
      if (route.children != null && route.children && route.children.length) {
        route.children = filterAsyncRouter(route.children, route, type);
@@ -82,11 +81,11 @@
      return true;
    });
  };
  const filterChildren = (childrenMap: RouteOption[], lastRouter?: RouteOption): RouteOption[] => {
    let children: RouteOption[] = [];
  const filterChildren = (childrenMap: RouteRecordRaw[], lastRouter?: RouteRecordRaw): RouteRecordRaw[] => {
    let children: RouteRecordRaw[] = [];
    childrenMap.forEach((el) => {
      if (el.children && el.children.length) {
        if (el.component === 'ParentView' && !lastRouter) {
        if (el.component?.toString() === 'ParentView' && !lastRouter) {
          el.children.forEach((c) => {
            c.path = el.path + '/' + c.path;
            if (c.children && c.children.length) {
@@ -101,8 +100,8 @@
      if (lastRouter) {
        el.path = lastRouter.path + '/' + el.path;
        if (el.children && el.children.length) {
          children = children.concat(filterChildren(el.children, el))
          return
          children = children.concat(filterChildren(el.children, el));
          return;
        }
      }
      children = children.concat(el);
@@ -113,8 +112,8 @@
});
// åŠ¨æ€è·¯ç”±éåŽ†ï¼ŒéªŒè¯æ˜¯å¦å…·å¤‡æƒé™
export const filterDynamicRoutes = (routes: RouteOption[]) => {
  const res: RouteOption[] = [];
export const filterDynamicRoutes = (routes: RouteRecordRaw[]) => {
  const res: RouteRecordRaw[] = [];
  routes.forEach((route) => {
    if (route.permissions) {
      if (auth.hasPermiOr(route.permissions)) {
src/store/modules/settings.ts
@@ -2,26 +2,34 @@
import defaultSettings from '@/settings';
import { SettingTypeEnum } from '@/enums/SettingTypeEnum';
import { useDynamicTitle } from '@/utils/dynamicTitle';
import { Ref } from 'vue';
export const useSettingsStore = defineStore('setting', () => {
  const storageSetting = JSON.parse(localStorage.getItem('layout-setting') || '{}');
  const title = ref<string>(defaultSettings.title);
  const theme = ref<string>(storageSetting.theme || defaultSettings.theme);
  const sideTheme = ref<string>(storageSetting.sideTheme || defaultSettings.sideTheme);
  const showSettings = ref<boolean>(storageSetting.showSettings || defaultSettings.showSettings);
  const topNav = ref<boolean>(storageSetting.topNav === undefined ? defaultSettings.topNav : storageSetting.topNav);
  const tagsView = ref<boolean>(storageSetting.tagsView === undefined ? defaultSettings.tagsView : storageSetting.tagsView);
  const fixedHeader = ref<boolean>(storageSetting.fixedHeader === undefined ? defaultSettings.fixedHeader : storageSetting.fixedHeader);
  const sidebarLogo = ref<boolean>(storageSetting.sidebarLogo === undefined ? defaultSettings.sidebarLogo : storageSetting.sidebarLogo);
  const dynamicTitle = ref<boolean>(storageSetting.dynamicTitle === undefined ? defaultSettings.dynamicTitle : storageSetting.dynamicTitle);
  const animationEnable = ref<boolean>(
    storageSetting.animationEnable === undefined ? defaultSettings.animationEnable : storageSetting.animationEnable
  );
  const dark = ref<boolean>(storageSetting.dark || defaultSettings.dark);
  const prop: { [key: string]: Ref<any> } = {
    title: ref<string>(''),
    theme: ref<string>(storageSetting.theme || defaultSettings.theme),
    sideTheme: ref<string>(storageSetting.sideTheme || defaultSettings.sideTheme),
    showSettings: ref<boolean>(storageSetting.showSettings || defaultSettings.showSettings),
    topNav: ref<boolean>(storageSetting.topNav === undefined ? defaultSettings.topNav : storageSetting.topNav),
    tagsView: ref<boolean>(storageSetting.tagsView === undefined ? defaultSettings.tagsView : storageSetting.tagsView),
    fixedHeader: ref<boolean>(storageSetting.fixedHeader === undefined ? defaultSettings.fixedHeader : storageSetting.fixedHeader),
    sidebarLogo: ref<boolean>(storageSetting.sidebarLogo === undefined ? defaultSettings.sidebarLogo : storageSetting.sidebarLogo),
    dynamicTitle: ref<boolean>(storageSetting.dynamicTitle === undefined ? defaultSettings.dynamicTitle : storageSetting.dynamicTitle),
    animationEnable: ref<boolean>(storageSetting.animationEnable === undefined ? defaultSettings.animationEnable : storageSetting.animationEnable),
    dark: ref<boolean>(storageSetting.dark || defaultSettings.dark)
    theme,
    sideTheme,
    showSettings,
    topNav,
    tagsView,
    fixedHeader,
    sidebarLogo,
    dynamicTitle,
    animationEnable,
    dark
  };
  const { title, theme, sideTheme, showSettings, topNav, tagsView, fixedHeader, sidebarLogo, dynamicTitle, animationEnable, dark } = prop;
  // actions
  const changeSetting = (param: { key: SettingTypeEnum; value: any }) => {
src/store/modules/tagsView.ts
@@ -11,7 +11,7 @@
  };
  const addIframeView = (view: TagView): void => {
    if (iframeViews.value.some((v) => v.path === view.path)) return;
    if (iframeViews.value.some((v: TagView) => v.path === view.path)) return;
    iframeViews.value.push(
      Object.assign({}, view, {
        title: view.meta?.title || 'no-name'
@@ -20,12 +20,12 @@
  };
  const delIframeView = (view: TagView): Promise<TagView[]> => {
    return new Promise((resolve) => {
      iframeViews.value = iframeViews.value.filter((item) => item.path !== view.path);
      iframeViews.value = iframeViews.value.filter((item: TagView) => item.path !== view.path);
      resolve([...iframeViews.value]);
    });
  };
  const addVisitedView = (view: TagView): void => {
    if (visitedViews.value.some((v) => v.path === view.path)) return;
    if (visitedViews.value.some((v: TagView) => v.path === view.path)) return;
    visitedViews.value.push(
      Object.assign({}, view, {
        title: view.meta?.title || 'no-name'
@@ -80,7 +80,7 @@
  const delOthersVisitedViews = (view: TagView): Promise<TagView[]> => {
    return new Promise((resolve) => {
      visitedViews.value = visitedViews.value.filter((v) => {
      visitedViews.value = visitedViews.value.filter((v: TagView) => {
        return v.meta?.affix || v.path === view.path;
      });
      resolve([...visitedViews.value]);
@@ -111,7 +111,7 @@
  };
  const delAllVisitedViews = (): Promise<TagView[]> => {
    return new Promise((resolve) => {
      visitedViews.value = visitedViews.value.filter((tag) => tag.meta?.affix);
      visitedViews.value = visitedViews.value.filter((tag: TagView) => tag.meta?.affix);
      resolve([...visitedViews.value]);
    });
  };
@@ -123,7 +123,7 @@
    });
  };
  const updateVisitedView = (view: TagView): void => {
  const updateVisitedView = (view: TagView | RouteLocationNormalized): void => {
    for (let v of visitedViews.value) {
      if (v.path === view.path) {
        v = Object.assign(v, view);
@@ -133,11 +133,11 @@
  };
  const delRightTags = (view: TagView): Promise<TagView[]> => {
    return new Promise((resolve) => {
      const index = visitedViews.value.findIndex((v) => v.path === view.path);
      const index = visitedViews.value.findIndex((v: TagView) => v.path === view.path);
      if (index === -1) {
        return;
      }
      visitedViews.value = visitedViews.value.filter((item, idx) => {
      visitedViews.value = visitedViews.value.filter((item: TagView, idx: number) => {
        if (idx <= index || (item.meta && item.meta.affix)) {
          return true;
        }
@@ -152,11 +152,11 @@
  };
  const delLeftTags = (view: TagView): Promise<TagView[]> => {
    return new Promise((resolve) => {
      const index = visitedViews.value.findIndex((v) => v.path === view.path);
      const index = visitedViews.value.findIndex((v: TagView) => v.path === view.path);
      if (index === -1) {
        return;
      }
      visitedViews.value = visitedViews.value.filter((item, idx) => {
      visitedViews.value = visitedViews.value.filter((item: TagView, idx: number) => {
        if (idx >= index || (item.meta && item.meta.affix)) {
          return true;
        }
@@ -170,7 +170,7 @@
    });
  };
  const addCachedView = (view: TagView): void => {
  const addCachedView = (view: TagView | RouteLocationNormalized): void => {
    const viewName = view.name as string;
    if (!viewName) return;
    if (cachedViews.value.includes(viewName)) return;
src/types/element.d.ts
@@ -1,12 +1,12 @@
import type * as ep from 'element-plus';
declare global {
  declare type ElTagType = '' | 'success' | 'warning' | 'info' | 'danger' | 'default' | 'primary';
  declare type ElFormInstance = InstanceType<typeof ep.ElForm>;
  declare type ElTableInstance = InstanceType<typeof ep.ElTable>;
  declare type ElFormInstance = ep.FormInstance;
  declare type ElTableInstance = ep.TableInstance;
  declare type ElUploadInstance = ep.UploadInstance;
  declare type ElTreeInstance = InstanceType<typeof ep.ElTree>;
  declare type ElTreeSelectInstance = InstanceType<typeof ep.ElTreeSelect>;
  declare type ElSelectInstance = InstanceType<typeof ep.ElSelect>;
  declare type ElUploadInstance = InstanceType<typeof ep.ElUpload>;
  declare type ElCardInstance = InstanceType<typeof ep.ElCard>;
  declare type ElDialogInstance = InstanceType<typeof ep.ElDialog>;
  declare type ElInputInstance = InstanceType<typeof ep.ElInput>;
src/types/env.d.ts
@@ -1,8 +1,9 @@
declare module '*.vue' {
  import { DefineComponent } from 'vue';
  const component: DefineComponent<{}, {}, any>;
  export default component;
  const Component: DefineComponent<{}, {}, any>;
  export default Component;
}
declare module '*.avif' {
  const src: string;
  export default src;
src/types/global.d.ts
@@ -1,4 +1,4 @@
import type { ComponentInternalInstance as ComponentInstance, PropType as VuePropType } from 'vue';
import type { ComponentInternalInstance as ComponentInstance, PropType as VuePropType } from 'vue/runtime-core';
declare global {
  /** vue Instance */
@@ -87,5 +87,76 @@
    pageNum: number;
    pageSize: number;
  }
  declare type DefaultSettings = {
    /**
     * ç½‘页标题
     */
    title: string;
    /**
     * ä¾§è¾¹æ ä¸»é¢˜ theme-dark | theme-light
     */
    sideTheme?: string;
    /**
     * æ˜¯å¦æ˜¾ç¤ºç³»ç»Ÿå¸ƒå±€è®¾ç½®
     */
    showSettings?: boolean;
    /**
     * æ˜¯å¦æ˜¾ç¤ºé¡¶éƒ¨å¯¼èˆª
     */
    topNav?: boolean;
    /**
     * æ˜¯å¦æ˜¾ç¤ºå¤šæ ‡ç­¾å¯¼èˆª
     */
    tagsView?: boolean;
    /**
     * æ˜¯å¦å›ºå®šå¤´éƒ¨
     */
    fixedHeader?: boolean;
    /**
     * æ˜¯å¦æ˜¾ç¤ºä¾§è¾¹æ Logo
     */
    sidebarLogo?: boolean;
    /**
     * å¯¼èˆªæ å¸ƒå±€
     */
    layout?: string;
    /**
     * ä¸»é¢˜æ¨¡å¼
     */
    theme?: string;
    /**
     * å¸ƒå±€å¤§å°
     */
    size?: string;
    /**
     * è¯­è¨€
     */
    language?: string;
    /**
     * æ˜¯å¦æ˜¾ç¤ºåŠ¨æ€æ ‡é¢˜
     */
    dynamicTitle?: boolean;
    /**
     * æ˜¯å¦å¯ç”¨åŠ¨ç”»æ•ˆæžœ
     */
    animationEnable?: boolean;
    /**
     *  æ˜¯å¦å¯ç”¨æš—黑模式
     *
     * true:暗黑模式
     * false: æ˜Žäº®æ¨¡å¼
     */
    dark?: boolean;
    errorLog?: string;
  };
}
export {};
src/types/router.d.ts
@@ -1,36 +1,34 @@
import { RouteRecordRaw } from 'vue-router';
import { LocationQuery, type RouteMeta as VRouteMeta } from 'vue-router';
declare module 'vue-router' {
  declare type RouteOption = {
    hidden?: boolean;
  interface RouteMeta extends VRouteMeta {
    link?: string;
    title?: string;
    affix?: boolean;
    noCache?: boolean;
    activeMenu?: string;
    icon?: string;
    breadcrumb?: boolean;
  }
  interface _RouteRecordBase {
    hidden?: boolean | string | number;
    permissions?: string[];
    roles?: string[];
    component?: any;
    children?: RouteOption[];
    alwaysShow?: boolean;
    parentPath?: string;
    meta?: {
      title: string;
      icon: string;
    };
    query?: string;
  } & RouteRecordRaw;
  declare interface _RouteLocationBase {
    children?: RouteOption[];
  }
  declare interface RouteLocationOptions {
  interface _RouteLocationBase {
    children?: _RouteRecordBase[];
    path?: string;
  }
  interface TagView {
    fullPath?: string;
  }
  declare interface TagView extends Partial<_RouteLocationBase> {
    name?: string;
    path?: string;
    title?: string;
    meta?: {
      link?: string;
      title?: string;
      affix?: boolean;
      noCache?: boolean;
    };
    meta?: RouteMeta;
    query?: LocationQuery;
  }
}
src/types/setting.d.ts
ÎļþÒÑɾ³ý
src/utils/propTypes.ts
@@ -3,6 +3,7 @@
type PropTypes = VueTypesInterface & {
  readonly style: VueTypeValidableDef<CSSProperties>;
  readonly fieldOption: VueTypeValidableDef<Array<FieldOption>>;
};
const propTypes = createTypes({
src/utils/request.ts
@@ -89,7 +89,6 @@
    return config;
  },
  (error: any) => {
    console.log(error);
    return Promise.reject(error);
  }
);
@@ -138,7 +137,6 @@
      }
      return Promise.reject('无效的会话,或者会话已过期,请重新登录。');
    } else if (code === HttpStatus.SERVER_ERROR) {
      console.log(msg);
      ElMessage({ message: msg, type: 'error' });
      return Promise.reject(new Error(msg));
    } else if (code === HttpStatus.WARN) {
src/utils/websocket.ts
@@ -20,7 +20,7 @@
import { getToken } from '@/utils/auth';
import useNoticeStore from '@/store/modules/notice';
import { ElNotification } from "element-plus";
import { ElNotification } from 'element-plus';
const { addNotice } = useNoticeStore();
@@ -135,7 +135,7 @@
      message: e.data,
      type: 'success',
      duration: 3000
    })
    });
    return e.data;
  };
};
src/views/demo/demo/index.vue
@@ -1,9 +1,9 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="mb-[10px]" v-show="showSearch">
      <div v-show="showSearch" class="mb-[10px]">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
            <el-form-item label="部门id" prop="deptId">
              <el-input v-model="queryParams.deptId" placeholder="请输入部门id" clearable style="width: 240px" @keyup.enter="handleQuery" />
            </el-form-item>
@@ -32,26 +32,26 @@
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['demo:demo:add']">新增</el-button>
            <el-button v-hasPermi="['demo:demo:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['demo:demo:edit']">修改</el-button>
            <el-button v-hasPermi="['demo:demo:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['demo:demo:remove']"
            <el-button v-hasPermi="['demo:demo:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"
              >删除</el-button
            >
          </el-col>
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['demo:demo:export']">导出</el-button>
            <el-button v-hasPermi="['demo:demo:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="demoList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="主键" align="center" prop="id" v-if="true" />
        <el-table-column v-if="true" label="主键" align="center" prop="id" />
        <el-table-column label="部门id" align="center" prop="deptId" />
        <el-table-column label="用户id" align="center" prop="userId" />
        <el-table-column label="排序号" align="center" prop="orderNum" />
@@ -60,19 +60,19 @@
        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['demo:demo:edit']"></el-button>
              <el-button v-hasPermi="['demo:demo:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['demo:demo:remove']"></el-button>
              <el-button v-hasPermi="['demo:demo:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹æµ‹è¯•å•å¯¹è¯æ¡† -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
      <el-form ref="demoFormRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="部门id" prop="deptId">
          <el-input v-model="form.deptId" placeholder="请输入部门id" />
@@ -129,8 +129,8 @@
  userId: undefined,
  orderNum: undefined,
  testKey: undefined,
  value: undefined,
}
  value: undefined
};
const data = reactive<PageData<DemoForm, DemoQuery>>({
  form: { ...initFormData },
  queryParams: {
@@ -140,27 +140,15 @@
    userId: undefined,
    orderNum: undefined,
    testKey: undefined,
    value: undefined,
    value: undefined
  },
  rules: {
    id: [
      { required: true, message: "主键不能为空", trigger: "blur" }
    ],
    deptId: [
      { required: true, message: "部门id不能为空", trigger: "blur" }
    ],
    userId: [
      { required: true, message: "用户id不能为空", trigger: "blur" }
    ],
    orderNum: [
      { required: true, message: "排序号不能为空", trigger: "blur" }
    ],
    testKey: [
      { required: true, message: "key键不能为空", trigger: "blur" }
    ],
    value: [
      { required: true, message: "值不能为空", trigger: "blur" }
    ],
    id: [{ required: true, message: '主键不能为空', trigger: 'blur' }],
    deptId: [{ required: true, message: '部门id不能为空', trigger: 'blur' }],
    userId: [{ required: true, message: '用户id不能为空', trigger: 'blur' }],
    orderNum: [{ required: true, message: '排序号不能为空', trigger: 'blur' }],
    testKey: [{ required: true, message: 'key键不能为空', trigger: 'blur' }],
    value: [{ required: true, message: '值不能为空', trigger: 'blur' }]
  }
});
@@ -173,55 +161,55 @@
  demoList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
};
/** å–消按钮 */
const cancel = () => {
  reset();
  dialog.visible = false;
}
};
/** è¡¨å•重置 */
const reset = () => {
  form.value = { ...initFormData };
  demoFormRef.value?.resetFields();
}
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value?.resetFields();
  handleQuery();
}
};
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: DemoVO[]) => {
  ids.value = selection.map(item => item.id);
  ids.value = selection.map((item) => item.id);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
};
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
  reset();
  dialog.visible = true;
  dialog.title = "添加测试单";
}
  dialog.title = '添加测试单';
};
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = async (row?: DemoVO) => {
  reset();
  const _id = row?.id || ids.value[0]
  const _id = row?.id || ids.value[0];
  const res = await getDemo(_id);
  Object.assign(form.value, res.data);
  dialog.visible = true;
  dialog.title = "修改测试单";
}
  dialog.title = '修改测试单';
};
/** æäº¤æŒ‰é’® */
const submitForm = () => {
@@ -229,32 +217,36 @@
    if (valid) {
      buttonLoading.value = true;
      if (form.value.id) {
        await updateDemo(form.value).finally(() => buttonLoading.value = false);
        await updateDemo(form.value).finally(() => (buttonLoading.value = false));
      } else {
        await addDemo(form.value).finally(() => buttonLoading.value = false);
        await addDemo(form.value).finally(() => (buttonLoading.value = false));
      }
      proxy?.$modal.msgSuccess("修改成功");
      proxy?.$modal.msgSuccess('修改成功');
      dialog.visible = false;
      await getList();
    }
  });
}
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: DemoVO) => {
  const _ids = row?.id || ids.value;
  await proxy?.$modal.confirm('是否确认删除测试单编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
  await proxy?.$modal.confirm('是否确认删除测试单编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
  await delDemo(_ids);
  proxy?.$modal.msgSuccess("删除成功");
  proxy?.$modal.msgSuccess('删除成功');
  await getList();
}
};
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
  proxy?.download('demo/demo/export', {
    ...queryParams.value
  }, `demo_${new Date().getTime()}.xlsx`)
}
  proxy?.download(
    'demo/demo/export',
    {
      ...queryParams.value
    },
    `demo_${new Date().getTime()}.xlsx`
  );
};
onMounted(() => {
  getList();
src/views/demo/tree/index.vue
@@ -1,9 +1,9 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="mb-[10px]" v-show="showSearch">
      <div v-show="showSearch" class="mb-[10px]">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
            <el-form-item label="树节点名" prop="treeName">
              <el-input v-model="queryParams.treeName" placeholder="请输入树节点名" clearable style="width: 240px" @keyup.enter="handleQuery" />
            </el-form-item>
@@ -20,21 +20,21 @@
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['demo:tree:add']">新增</el-button>
            <el-button v-hasPermi="['demo:tree:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table
        ref="treeTableRef"
        v-loading="loading"
        :data="treeList"
        row-key="id"
        :default-expand-all="isExpandAll"
        :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
        ref="treeTableRef"
        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
      >
        <el-table-column label="父id" align="center" prop="parentId" />
        <el-table-column label="部门id" align="center" prop="deptId" />
@@ -43,20 +43,20 @@
        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['demo:tree:edit']" />
              <el-button v-hasPermi="['demo:tree:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)" />
            </el-tooltip>
            <el-tooltip content="新增" placement="top">
              <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['demo:tree:add']" />
              <el-button v-hasPermi="['demo:tree:add']" link type="primary" icon="Plus" @click="handleAdd(scope.row)" />
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['demo:tree:remove']" />
              <el-button v-hasPermi="['demo:tree:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)" />
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹æµ‹è¯•æ ‘å¯¹è¯æ¡† -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
      <el-form ref="treeFormRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="父id" prop="parentId">
          <el-tree-select
@@ -89,18 +89,16 @@
</template>
<script setup name="Tree" lang="ts">
import { listTree, getTree, delTree, addTree, updateTree } from "@/api/demo/tree";
import { listTree, getTree, delTree, addTree, updateTree } from '@/api/demo/tree';
import { TreeVO, TreeQuery, TreeForm } from '@/api/demo/tree/types';
type TreeOption = {
  id: number;
  treeName: string;
  children?: TreeOption[];
}
};
const { proxy } = getCurrentInstance() as ComponentInternalInstance;;
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const treeList = ref<TreeVO[]>([]);
const treeOptions = ref<TreeOption[]>([]);
@@ -111,46 +109,35 @@
const queryFormRef = ref<ElFormInstance>();
const treeFormRef = ref<ElFormInstance>();
const treeTableRef = ref<ElTableInstance>()
const treeTableRef = ref<ElTableInstance>();
const dialog = reactive<DialogOption>({
    visible: false,
    title: ''
  visible: false,
  title: ''
});
const initFormData: TreeForm = {
    id: undefined,
    parentId: undefined,
    deptId: undefined,
    userId: undefined,
    treeName: undefined,
}
  id: undefined,
  parentId: undefined,
  deptId: undefined,
  userId: undefined,
  treeName: undefined
};
const data = reactive<PageData<TreeForm, TreeQuery>>({
  form: {...initFormData},
  form: { ...initFormData },
  queryParams: {
    parentId: undefined,
    deptId: undefined,
    userId: undefined,
    treeName: undefined,
    treeName: undefined
  },
  rules: {
    id: [
      { required: true, message: "主键不能为空", trigger: "blur" }
    ],
    parentId: [
      { required: true, message: "父id不能为空", trigger: "blur" }
    ],
    deptId: [
      { required: true, message: "部门id不能为空", trigger: "blur" }
    ],
    userId: [
      { required: true, message: "用户id不能为空", trigger: "blur" }
    ],
    treeName: [
      { required: true, message: "值不能为空", trigger: "blur" }
    ],
    id: [{ required: true, message: '主键不能为空', trigger: 'blur' }],
    parentId: [{ required: true, message: '父id不能为空', trigger: 'blur' }],
    deptId: [{ required: true, message: '部门id不能为空', trigger: 'blur' }],
    userId: [{ required: true, message: '用户id不能为空', trigger: 'blur' }],
    treeName: [{ required: true, message: '值不能为空', trigger: 'blur' }]
  }
});
@@ -160,44 +147,44 @@
const getList = async () => {
  loading.value = true;
  const res = await listTree(queryParams.value);
  const data = proxy?.handleTree<TreeVO>(res.data, "id", "parentId");
  const data = proxy?.handleTree<TreeVO>(res.data, 'id', 'parentId');
  if (data) {
    treeList.value = data;
    loading.value = false;
  }
}
};
/** æŸ¥è¯¢æµ‹è¯•树下拉树结构 */
const getTreeselect = async () => {
  const res = await listTree();
  treeOptions.value = [];
  const data: TreeOption = { id: 0, treeName: '顶级节点', children: [] };
  data.children = proxy?.handleTree<TreeOption>(res.data, "id", "parentId");
  data.children = proxy?.handleTree<TreeOption>(res.data, 'id', 'parentId');
  treeOptions.value.push(data);
}
};
// å–消按钮
const cancel = () => {
  reset();
  dialog.visible = false;
}
};
// è¡¨å•重置
const reset = () => {
  form.value = {...initFormData}
  form.value = { ...initFormData };
  treeFormRef.value?.resetFields();
}
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  getList();
}
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value?.resetFields();
  handleQuery();
}
};
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = (row?: TreeVO) => {
@@ -209,22 +196,22 @@
    form.value.parentId = 0;
  }
  dialog.visible = true;
  dialog.title = "添加测试树";
}
  dialog.title = '添加测试树';
};
/** å±•å¼€/折叠操作 */
const handleToggleExpandAll = () => {
  isExpandAll.value = !isExpandAll.value;
  toggleExpandAll(treeList.value, isExpandAll.value)
}
  toggleExpandAll(treeList.value, isExpandAll.value);
};
/** å±•å¼€/折叠操作 */
const toggleExpandAll = (data: TreeVO[], status: boolean) => {
  data.forEach((item) => {
    treeTableRef.value?.toggleRowExpansion(item, status)
    if (item.children && item.children.length > 0) toggleExpandAll(item.children, status)
  })
}
    treeTableRef.value?.toggleRowExpansion(item, status);
    if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
  });
};
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = async (row: TreeVO) => {
@@ -236,8 +223,8 @@
  const res = await getTree(row.id);
  Object.assign(form.value, res.data);
  dialog.visible = true;
  dialog.title = "修改测试树";
}
  dialog.title = '修改测试树';
};
/** æäº¤æŒ‰é’® */
const submitForm = () => {
@@ -245,25 +232,25 @@
    if (valid) {
      buttonLoading.value = true;
      if (form.value.id) {
        await updateTree(form.value).finally(() => buttonLoading.value = false);
        await updateTree(form.value).finally(() => (buttonLoading.value = false));
      } else {
        await addTree(form.value).finally(() => buttonLoading.value = false);
        await addTree(form.value).finally(() => (buttonLoading.value = false));
      }
      proxy?.$modal.msgSuccess("操作成功");
      proxy?.$modal.msgSuccess('操作成功');
      dialog.visible = false;
      await getList();
    }
  });
}
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row: TreeVO) => {
  await proxy?.$modal.confirm('是否确认删除测试树编号为"' + row.id + '"的数据项?');
  loading.value = true;
  await delTree(row.id).finally(() => loading.value = false);
  await delTree(row.id).finally(() => (loading.value = false));
  await getList();
  proxy?.$modal.msgSuccess("删除成功");
}
  proxy?.$modal.msgSuccess('删除成功');
};
onMounted(() => {
  getList();
src/views/error/401.vue
@@ -24,11 +24,11 @@
let { proxy } = getCurrentInstance() as ComponentInternalInstance;
const errGif = ref(errImage + "?" + +new Date());
const errGif = ref(errImage + '?' + +new Date());
function back() {
  if (proxy?.$route.query.noGoBack) {
    proxy.$router.push({ path: "/" });
    proxy.$router.push({ path: '/' });
  } else {
    proxy?.$router.go(-1);
  }
src/views/error/404.vue
@@ -23,13 +23,13 @@
<script setup lang="ts">
let message = computed(() => {
  return '找不到网页!'
})
  return '找不到网页!';
});
</script>
<style lang="scss" scoped>
.wscn-http404-container{
  transform: translate(-50%,-50%);
.wscn-http404-container {
  transform: translate(-50%, -50%);
  position: absolute;
  top: 40%;
  left: 50%;
src/views/index.vue
@@ -99,13 +99,13 @@
import { initWebSocket } from '@/utils/websocket';
onMounted(() => {
  let protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://'
  initWebSocket(protocol + window.location.host + import.meta.env.VITE_APP_BASE_API + "/resource/websocket");
  let protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
  initWebSocket(protocol + window.location.host + import.meta.env.VITE_APP_BASE_API + '/resource/websocket');
});
const goTarget = (url:string) => {
  window.open(url, '__blank')
}
const goTarget = (url: string) => {
  window.open(url, '__blank');
};
</script>
<style scoped lang="scss">
@@ -131,7 +131,7 @@
    margin: 0;
  }
  font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-family: 'open sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  font-size: 13px;
  color: #676a6c;
  overflow-x: hidden;
src/views/login.vue
@@ -2,7 +2,7 @@
  <div class="login">
    <el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
      <h3 class="title">RuoYi-Vue-Plus多租户管理系统</h3>
      <el-form-item prop="tenantId" v-if="tenantEnabled">
      <el-form-item v-if="tenantEnabled" prop="tenantId">
        <el-select v-model="loginForm.tenantId" filterable placeholder="请选择/输入公司名称" style="width: 100%">
          <el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" :value="item.tenantId"></el-option>
          <template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template>
@@ -18,16 +18,16 @@
          <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
        </el-input>
      </el-form-item>
      <el-form-item prop="code" v-if="captchaEnabled">
      <el-form-item v-if="captchaEnabled" prop="code">
        <el-input v-model="loginForm.code" size="large" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter="handleLogin">
          <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
        </el-input>
        <div class="login-code">
          <img :src="codeUrl" @click="getCode" class="login-code-img" />
          <img :src="codeUrl" class="login-code-img" @click="getCode" />
        </div>
      </el-form-item>
      <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
      <el-form-item style="float: right;">
      <el-checkbox v-model="loginForm.rememberMe" style="margin: 0px 0px 25px 0px">记住密码</el-checkbox>
      <el-form-item style="float: right">
        <el-button circle title="微信登录" @click="doSocialLogin('wechat')">
          <svg-icon icon-class="wechat" />
        </el-button>
@@ -41,12 +41,12 @@
          <svg-icon icon-class="github" />
        </el-button>
      </el-form-item>
      <el-form-item style="width:100%;">
        <el-button :loading="loading" size="large" type="primary" style="width:100%;" @click.prevent="handleLogin">
      <el-form-item style="width: 100%">
        <el-button :loading="loading" size="large" type="primary" style="width: 100%" @click.prevent="handleLogin">
          <span v-if="!loading">登 å½•</span>
          <span v-else>登 å½• ä¸­...</span>
        </el-button>
        <div style="float: right;" v-if="register">
        <div v-if="register" style="float: right">
          <router-link class="link-type" :to="'/register'">立即注册</router-link>
        </div>
      </el-form-item>
@@ -64,7 +64,7 @@
import { useUserStore } from '@/store/modules/user';
import { LoginData, TenantVO } from '@/api/types';
import { to } from 'await-to-js';
import { HttpStatus } from "@/enums/RespEnum";
import { HttpStatus } from '@/enums/RespEnum';
const userStore = useUserStore();
const router = useRouter();
@@ -79,7 +79,7 @@
} as LoginData);
const loginRules: ElFormRules = {
  tenantId: [{ required: true, trigger: "blur", message: "请输入您的租户编号" }],
  tenantId: [{ required: true, trigger: 'blur', message: '请输入您的租户编号' }],
  username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
  password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }],
  code: [{ required: true, trigger: 'change', message: '请输入验证码' }]
@@ -92,7 +92,6 @@
// ç§Ÿæˆ·å¼€å…³
const tenantEnabled = ref(true);
// æ³¨å†Œå¼€å…³
const register = ref(false);
const redirect = ref(undefined);
@@ -100,9 +99,13 @@
// ç§Ÿæˆ·åˆ—表
const tenantList = ref<TenantVO[]>([]);
watch(() => router.currentRoute.value, (newRoute: any) => {
  redirect.value = newRoute.query && newRoute.query.redirect;
}, { immediate: true });
watch(
  () => router.currentRoute.value,
  (newRoute: any) => {
    redirect.value = newRoute.query && newRoute.query.redirect;
  },
  { immediate: true }
);
const handleLogin = () => {
  loginRef.value?.validate(async (valid: boolean, fields: any) => {
@@ -110,13 +113,13 @@
      loading.value = true;
      // å‹¾é€‰äº†éœ€è¦è®°ä½å¯†ç è®¾ç½®åœ¨ localStorage ä¸­è®¾ç½®è®°ä½ç”¨æˆ·åå’Œå¯†ç 
      if (loginForm.value.rememberMe) {
        localStorage.setItem("tenantId", String(loginForm.value.tenantId));
        localStorage.setItem('tenantId', String(loginForm.value.tenantId));
        localStorage.setItem('username', String(loginForm.value.username));
        localStorage.setItem('password', String(loginForm.value.password));
        localStorage.setItem('rememberMe', String(loginForm.value.rememberMe));
      } else {
        // å¦åˆ™ç§»é™¤
        localStorage.removeItem("tenantId");
        localStorage.removeItem('tenantId');
        localStorage.removeItem('username');
        localStorage.removeItem('password');
        localStorage.removeItem('rememberMe');
@@ -153,7 +156,7 @@
};
const getLoginData = () => {
  const tenantId = localStorage.getItem("tenantId");
  const tenantId = localStorage.getItem('tenantId');
  const username = localStorage.getItem('username');
  const password = localStorage.getItem('password');
  const rememberMe = localStorage.getItem('rememberMe');
@@ -163,8 +166,7 @@
    password: password === null ? String(loginForm.value.password) : String(password),
    rememberMe: rememberMe === null ? false : Boolean(rememberMe)
  } as LoginData;
}
};
/**
 * èŽ·å–ç§Ÿæˆ·åˆ—è¡¨
@@ -178,12 +180,15 @@
      loginForm.value.tenantId = tenantList.value[0].tenantId;
    }
  }
}
};
//检测租户选择框的变化
watch(() => loginForm.value.tenantId, () => {
  localStorage.setItem("tenantId", String(loginForm.value.tenantId))
});
watch(
  () => loginForm.value.tenantId,
  () => {
    localStorage.setItem('tenantId', String(loginForm.value.tenantId));
  }
);
/**
 * ç¬¬ä¸‰æ–¹ç™»å½•
@@ -200,8 +205,6 @@
  });
};
onMounted(() => {
  getCode();
  initTenantList();
@@ -215,7 +218,7 @@
  justify-content: center;
  align-items: center;
  height: 100%;
  background-image: url("../assets/images/login-background.jpg");
  background-image: url('../assets/images/login-background.jpg');
  background-size: cover;
}
src/views/monitor/cache/index.vue
@@ -4,8 +4,8 @@
      <el-col :span="24" class="card-box">
        <el-card shadow="hover">
          <template #header>
            <Monitor style="width: 1em; height: 1em; vertical-align: middle;" />
            <span style="vertical-align: middle;">基本信息</span>
            <Monitor style="width: 1em; height: 1em; vertical-align: middle" />
            <span style="vertical-align: middle">基本信息</span>
          </template>
          <div class="el-table el-table--enable-row-hover el-table--medium">
@@ -16,25 +16,25 @@
                    <div class="cell">Redis版本</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">{{ cache.info.redis_version }}</div>
                    <div v-if="cache.info" class="cell">{{ cache.info.redis_version }}</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell">运行模式</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">{{ cache.info.redis_mode === "standalone" ? "单机" : "集群" }}</div>
                    <div v-if="cache.info" class="cell">{{ cache.info.redis_mode === 'standalone' ? '单机' : '集群' }}</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell">端口</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">{{ cache.info.tcp_port }}</div>
                    <div v-if="cache.info" class="cell">{{ cache.info.tcp_port }}</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell">客户端数</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">{{ cache.info.connected_clients }}</div>
                    <div v-if="cache.info" class="cell">{{ cache.info.connected_clients }}</div>
                  </td>
                </tr>
                <tr>
@@ -42,25 +42,25 @@
                    <div class="cell">运行时间(天)</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">{{ cache.info.uptime_in_days }}</div>
                    <div v-if="cache.info" class="cell">{{ cache.info.uptime_in_days }}</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell">使用内存</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">{{ cache.info.used_memory_human }}</div>
                    <div v-if="cache.info" class="cell">{{ cache.info.used_memory_human }}</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell">使用CPU</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">{{ parseFloat(cache.info.used_cpu_user_children).toFixed(2) }}</div>
                    <div v-if="cache.info" class="cell">{{ parseFloat(cache.info.used_cpu_user_children).toFixed(2) }}</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell">内存配置</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">{{ cache.info.maxmemory_human }}</div>
                    <div v-if="cache.info" class="cell">{{ cache.info.maxmemory_human }}</div>
                  </td>
                </tr>
                <tr>
@@ -68,25 +68,25 @@
                    <div class="cell">AOF是否开启</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">{{ cache.info.aof_enabled === "0" ? "否" : "是" }}</div>
                    <div v-if="cache.info" class="cell">{{ cache.info.aof_enabled === '0' ? '否' : '是' }}</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell">RDB是否成功</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">{{ cache.info.rdb_last_bgsave_status }}</div>
                    <div v-if="cache.info" class="cell">{{ cache.info.rdb_last_bgsave_status }}</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell">Key数量</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.dbSize">{{ cache.dbSize }}</div>
                    <div v-if="cache.dbSize" class="cell">{{ cache.dbSize }}</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell">网络入口/出口</div>
                  </td>
                  <td class="el-table__cell is-leaf">
                    <div class="cell" v-if="cache.info">
                    <div v-if="cache.info" class="cell">
                      {{ cache.info.instantaneous_input_kbps }}kps/{{ cache.info.instantaneous_output_kbps }}kps
                    </div>
                  </td>
@@ -100,8 +100,8 @@
      <el-col :span="12" class="card-box">
        <el-card shadow="hover">
          <template #header>
            <PieChart style="width: 1em; height: 1em; vertical-align: middle;" />
            <span style="vertical-align: middle;">命令统计</span>
            <PieChart style="width: 1em; height: 1em; vertical-align: middle" />
            <span style="vertical-align: middle">命令统计</span>
          </template>
          <div class="el-table el-table--enable-row-hover el-table--medium">
            <div ref="commandstats" style="height: 420px" />
@@ -112,7 +112,7 @@
      <el-col :span="12" class="card-box">
        <el-card shadow="hover">
          <template #header>
            <Odometer style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">内存信息</span>
            <Odometer style="width: 1em; height: 1em; vertical-align: middle" /> <span style="vertical-align: middle">内存信息</span>
          </template>
          <div class="el-table el-table--enable-row-hover el-table--medium">
            <div ref="usedmemory" style="height: 420px" />
@@ -133,38 +133,38 @@
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const getList = async () => {
  proxy?.$modal.loading("正在加载缓存监控数据,请稍候!");
  proxy?.$modal.loading('正在加载缓存监控数据,请稍候!');
  const res = await getCache();
  proxy?.$modal.closeLoading();
  cache.value = res.data;
  const commandstatsIntance = echarts.init(commandstats.value, "macarons");
  const commandstatsIntance = echarts.init(commandstats.value, 'macarons');
  commandstatsIntance.setOption({
    tooltip: {
      trigger: "item",
      formatter: "{a} <br/>{b} : {c} ({d}%)"
      trigger: 'item',
      formatter: '{a} <br/>{b} : {c} ({d}%)'
    },
    series: [
      {
        name: "命令",
        type: "pie",
        roseType: "radius",
        name: '命令',
        type: 'pie',
        roseType: 'radius',
        radius: [15, 95],
        center: ["50%", "38%"],
        center: ['50%', '38%'],
        data: res.data.commandStats,
        animationEasing: "cubicInOut",
        animationEasing: 'cubicInOut',
        animationDuration: 1000
      }
    ]
  });
  const usedmemoryInstance = echarts.init(usedmemory.value, "macarons");
  const usedmemoryInstance = echarts.init(usedmemory.value, 'macarons');
  usedmemoryInstance.setOption({
    tooltip: {
      formatter: "{b} <br/>{a} : " + cache.value.info.used_memory_human
      formatter: '{b} <br/>{a} : ' + cache.value.info.used_memory_human
    },
    series: [
      {
        name: "峰值",
        type: "gauge",
        name: '峰值',
        type: 'gauge',
        min: 0,
        max: 1000,
        detail: {
@@ -173,19 +173,19 @@
        data: [
          {
            value: parseFloat(cache.value.info.used_memory_human),
            name: "内存消耗"
            name: '内存消耗'
          }
        ]
      }
    ]
  })
  window.addEventListener("resize",()=>{
    commandstatsIntance.resize()
    usedmemoryInstance.resize()
  });
}
  window.addEventListener('resize', () => {
    commandstatsIntance.resize();
    usedmemoryInstance.resize();
  });
};
onMounted(() => {
  getList();
})
});
</script>
src/views/monitor/logininfor/index.vue
@@ -1,14 +1,14 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="mb-[10px]" v-show="showSearch">
      <div v-show="showSearch" class="mb-[10px]">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
            <el-form-item label="登录地址" prop="ipaddr">
              <el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable style="width: 240px;" @keyup.enter="handleQuery" />
              <el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable style="width: 240px" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="用户名称" prop="userName">
              <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable style="width: 240px;" @keyup.enter="handleQuery" />
              <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="状态" prop="status">
              <el-select v-model="queryParams.status" placeholder="登录状态" clearable style="width: 240px">
@@ -39,22 +39,22 @@
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['monitor:logininfor:remove']">
            <el-button v-hasPermi="['monitor:logininfor:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
              åˆ é™¤
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" @click="handleClean" v-hasPermi="['monitor:logininfor:remove']">清空</el-button>
            <el-button v-hasPermi="['monitor:logininfor:remove']" type="danger" plain icon="Delete" @click="handleClean">清空</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Unlock" :disabled="single" @click="handleUnlock" v-hasPermi="['monitor:logininfor:unlock']">
            <el-button v-hasPermi="['monitor:logininfor:unlock']" type="primary" plain icon="Unlock" :disabled="single" @click="handleUnlock">
              è§£é”
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['monitor:logininfor:export']">导出</el-button>
            <el-button v-hasPermi="['monitor:logininfor:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
@@ -62,8 +62,8 @@
        ref="loginInfoTableRef"
        v-loading="loading"
        :data="loginInfoList"
        @selection-change="handleSelectionChange"
        :default-sort="defaultSort"
        @selection-change="handleSelectionChange"
        @sort-change="handleSortChange"
      >
        <el-table-column type="selection" width="55" align="center" />
@@ -99,18 +99,18 @@
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
    </el-card>
  </div>
</template>
<script setup name="Logininfor" lang="ts">
import { list, delLoginInfo, cleanLoginInfo, unlockLoginInfo } from "@/api/monitor/loginInfo";
import { LoginInfoQuery, LoginInfoVO } from "@/api/monitor/loginInfo/types";
import { list, delLoginInfo, cleanLoginInfo, unlockLoginInfo } from '@/api/monitor/loginInfo';
import { LoginInfoQuery, LoginInfoVO } from '@/api/monitor/loginInfo/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_device_type } = toRefs<any>(proxy?.useDict("sys_device_type"));
const { sys_common_status } = toRefs<any>(proxy?.useDict("sys_common_status"));
const { sys_device_type } = toRefs<any>(proxy?.useDict('sys_device_type'));
const { sys_common_status } = toRefs<any>(proxy?.useDict('sys_common_status'));
const loginInfoList = ref<LoginInfoVO[]>([]);
const loading = ref(true);
@@ -120,85 +120,89 @@
const multiple = ref(true);
const selectName = ref<Array<string>>([]);
const total = ref(0);
const dateRange = ref<[DateModelType,DateModelType]>(['', '']);
const defaultSort = ref<any>({ prop: "loginTime", order: "descending" });
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const defaultSort = ref<any>({ prop: 'loginTime', order: 'descending' });
const queryFormRef = ref<ElFormInstance>();
const loginInfoTableRef = ref<ElTableInstance>();
// æŸ¥è¯¢å‚æ•°
const queryParams = ref<LoginInfoQuery>({
    pageNum: 1,
    pageSize: 10,
    ipaddr: '',
    userName: '',
    status: '',
    orderByColumn: defaultSort.value.prop,
    isAsc: defaultSort.value.order
  pageNum: 1,
  pageSize: 10,
  ipaddr: '',
  userName: '',
  status: '',
  orderByColumn: defaultSort.value.prop,
  isAsc: defaultSort.value.order
});
/** æŸ¥è¯¢ç™»å½•日志列表 */
const getList = async () => {
    loading.value = true;
    const res = await list(proxy?.addDateRange(queryParams.value, dateRange.value));
    loginInfoList.value = res.rows;
    total.value = res.total;
    loading.value = false;
}
  loading.value = true;
  const res = await list(proxy?.addDateRange(queryParams.value, dateRange.value));
  loginInfoList.value = res.rows;
  total.value = res.total;
  loading.value = false;
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    queryParams.value.pageNum = 1;
    getList();
}
  queryParams.value.pageNum = 1;
  getList();
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
    dateRange.value = ['', ''];
    queryFormRef.value?.resetFields();
    queryParams.value.pageNum = 1;
    loginInfoTableRef.value?.sort(defaultSort.value.prop, defaultSort.value.order);
}
  dateRange.value = ['', ''];
  queryFormRef.value?.resetFields();
  queryParams.value.pageNum = 1;
  loginInfoTableRef.value?.sort(defaultSort.value.prop, defaultSort.value.order);
};
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: LoginInfoVO[]) => {
    ids.value = selection.map(item => item.infoId);
    multiple.value = !selection.length;
    single.value = selection.length != 1;
    selectName.value = selection.map(item => item.userName);
}
  ids.value = selection.map((item) => item.infoId);
  multiple.value = !selection.length;
  single.value = selection.length != 1;
  selectName.value = selection.map((item) => item.userName);
};
/** æŽ’序触发事件 */
const handleSortChange = (column: any) => {
    queryParams.value.orderByColumn = column.prop;
    queryParams.value.isAsc = column.order;
    getList();
}
  queryParams.value.orderByColumn = column.prop;
  queryParams.value.isAsc = column.order;
  getList();
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: LoginInfoVO) => {
    const infoIds = row?.infoId || ids.value;
    await proxy?.$modal.confirm('是否确认删除访问编号为"' + infoIds + '"的数据项?');
    await delLoginInfo(infoIds);
    await getList();
    proxy?.$modal.msgSuccess("删除成功");
}
  const infoIds = row?.infoId || ids.value;
  await proxy?.$modal.confirm('是否确认删除访问编号为"' + infoIds + '"的数据项?');
  await delLoginInfo(infoIds);
  await getList();
  proxy?.$modal.msgSuccess('删除成功');
};
/** æ¸…空按钮操作 */
const handleClean = async () => {
    await proxy?.$modal.confirm("是否确认清空所有登录日志数据项?");
    await cleanLoginInfo();
    await getList();
    proxy?.$modal.msgSuccess("清空成功");
}
  await proxy?.$modal.confirm('是否确认清空所有登录日志数据项?');
  await cleanLoginInfo();
  await getList();
  proxy?.$modal.msgSuccess('清空成功');
};
/** è§£é”æŒ‰é’®æ“ä½œ */
const handleUnlock = async () => {
    const username = selectName.value;
    await proxy?.$modal.confirm('是否确认解锁用户"' + username + '"数据项?');
    await unlockLoginInfo(username);
    proxy?.$modal.msgSuccess("用户" + username + "解锁成功");
}
  const username = selectName.value;
  await proxy?.$modal.confirm('是否确认解锁用户"' + username + '"数据项?');
  await unlockLoginInfo(username);
  proxy?.$modal.msgSuccess('用户' + username + '解锁成功');
};
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
    proxy?.download("monitor/logininfor/export", {
        ...queryParams.value,
    }, `config_${new Date().getTime()}.xlsx`);
}
  proxy?.download(
    'monitor/logininfor/export',
    {
      ...queryParams.value
    },
    `config_${new Date().getTime()}.xlsx`
  );
};
onMounted(() => {
    getList();
})
  getList();
});
</script>
src/views/monitor/online/index.vue
@@ -2,7 +2,7 @@
  <div class="p-2">
    <div class="mb-[10px]">
      <el-card shadow="hover">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true">
        <el-form ref="queryFormRef" :model="queryParams" :inline="true">
          <el-form-item label="登录地址" prop="ipaddr">
            <el-input v-model="queryParams.ipaddr" placeholder="请输入登录地址" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
@@ -20,7 +20,7 @@
      <el-table
        v-loading="loading"
        :data="onlineList.slice((queryParams.pageNum - 1) * queryParams.pageSize, queryParams.pageNum * queryParams.pageSize)"
        style="width: 100%;"
        style="width: 100%"
      >
        <el-table-column label="序号" width="50" type="index" align="center">
          <template #default="scope">
@@ -48,14 +48,14 @@
        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="强退" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleForceLogout(scope.row)" v-hasPermi="['monitor:online:forceLogout']">
              <el-button v-hasPermi="['monitor:online:forceLogout']" link type="primary" icon="Delete" @click="handleForceLogout(scope.row)">
              </el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" />
      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" />
    </el-card>
  </div>
</template>
@@ -67,7 +67,7 @@
import {to} from "await-to-js";
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_device_type } = toRefs<any>(proxy?.useDict("sys_device_type"));
const { sys_device_type } = toRefs<any>(proxy?.useDict('sys_device_type'));
const onlineList = ref<OnlineVO[]>([]);
const loading = ref(true);
@@ -89,17 +89,17 @@
  onlineList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value?.resetFields();
  handleQuery();
}
};
/** å¼ºé€€æŒ‰é’®æ“ä½œ */
const handleForceLogout = async (row: OnlineVO) => {
  const [err] = await to(proxy?.$modal.confirm('是否确认强退名称为"' + row.userName + '"的用户?') as any);
@@ -112,5 +112,5 @@
onMounted(() => {
  getList();
})
});
</script>
src/views/monitor/operlog/index.vue
@@ -3,15 +3,15 @@
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="mb-[10px]">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
            <el-form-item label="操作地址" prop="operIp">
              <el-input v-model="queryParams.operIp" placeholder="请输入操作地址" clearable style="width: 240px;" @keyup.enter="handleQuery"/>
              <el-input v-model="queryParams.operIp" placeholder="请输入操作地址" clearable style="width: 240px" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="系统模块" prop="title">
              <el-input v-model="queryParams.title" placeholder="请输入系统模块" clearable style="width: 240px;" @keyup.enter="handleQuery" />
              <el-input v-model="queryParams.title" placeholder="请输入系统模块" clearable style="width: 240px" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="操作人员" prop="operName">
              <el-input v-model="queryParams.operName" placeholder="请输入操作人员" clearable style="width: 240px;" @keyup.enter="handleQuery" />
              <el-input v-model="queryParams.operName" placeholder="请输入操作人员" clearable style="width: 240px" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="类型" prop="businessType">
              <el-select v-model="queryParams.businessType" placeholder="操作类型" clearable style="width: 240px">
@@ -47,17 +47,17 @@
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['monitor:operlog:remove']">
            <el-button v-hasPermi="['monitor:operlog:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
              åˆ é™¤
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="WarnTriangleFilled" @click="handleClean" v-hasPermi="['monitor:operlog:remove']">清空</el-button>
            <el-button v-hasPermi="['monitor:operlog:remove']" type="danger" plain icon="WarnTriangleFilled" @click="handleClean">清空</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['monitor:operlog:export']">导出</el-button>
            <el-button v-hasPermi="['monitor:operlog:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
@@ -65,8 +65,8 @@
        ref="operLogTableRef"
        v-loading="loading"
        :data="operlogList"
        @selection-change="handleSelectionChange"
        :default-sort="defaultSort"
        @selection-change="handleSelectionChange"
        @sort-change="handleSortChange"
      >
        <el-table-column type="selection" width="50" align="center" />
@@ -114,20 +114,20 @@
        <el-table-column label="操作" fixed="right" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="详细" placement="top">
              <el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['monitor:operlog:query']"> </el-button>
              <el-button v-hasPermi="['monitor:operlog:query']" link type="primary" icon="View" @click="handleView(scope.row)"> </el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
    </el-card>
    <!-- æ“ä½œæ—¥å¿—详细 -->
    <el-dialog title="操作日志详细" v-model="dialog.visible" width="700px" append-to-body>
    <el-dialog v-model="dialog.visible" title="操作日志详细" width="700px" append-to-body>
      <el-form :model="form" label-width="100px">
        <el-row>
          <el-col :span="24">
            <el-form-item label="登录信息:">{{ form.operName }} / {{form.deptName}} / {{ form.operIp }} / {{ form.operLocation }}</el-form-item>
            <el-form-item label="登录信息:">{{ form.operName }} / {{ form.deptName }} / {{ form.operIp }} / {{ form.operLocation }}</el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="请求信息:">{{ form.requestMethod }} {{ form.operUrl }}</el-form-item>
@@ -157,7 +157,7 @@
            <el-form-item label="操作时间:">{{ parseTime(form.operTime) }}</el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="异常信息:" v-if="form.status === 1">{{ form.errorMsg }}</el-form-item>
            <el-form-item v-if="form.status === 1" label="异常信息:">{{ form.errorMsg }}</el-form-item>
          </el-col>
        </el-row>
      </el-form>
@@ -175,7 +175,7 @@
import { OperLogForm, OperLogQuery, OperLogVO } from '@/api/monitor/operlog/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_oper_type, sys_common_status } = toRefs<any>(proxy?.useDict("sys_oper_type", "sys_common_status"));
const { sys_oper_type, sys_common_status } = toRefs<any>(proxy?.useDict('sys_oper_type', 'sys_common_status'));
const operlogList = ref<OperLogVO[]>([]);
const loading = ref(true);
@@ -184,7 +184,7 @@
const multiple = ref(true);
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const defaultSort = ref<any>({ prop: "operTime", order: "descending" });
const defaultSort = ref<any>({ prop: 'operTime', order: 'descending' });
const operLogTableRef = ref<ElTableInstance>();
const queryFormRef = ref<ElFormInstance>();
@@ -193,7 +193,6 @@
  visible: false,
  title: ''
});
const data = reactive<PageData<OperLogForm, OperLogQuery>>({
  form: {
@@ -240,63 +239,67 @@
  operlogList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
};
/** æ“ä½œæ—¥å¿—类型字典翻译 */
const typeFormat = (row: OperLogForm) => {
  return proxy?.selectDictLabel(sys_oper_type.value, row.businessType);
}
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  dateRange.value = ['', ''];
  queryFormRef.value?.resetFields();
  queryParams.value.pageNum = 1;
  operLogTableRef.value?.sort(defaultSort.value.prop, defaultSort.value.order);
}
};
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: OperLogVO[]) => {
  ids.value = selection.map(item => item.operId);
  ids.value = selection.map((item) => item.operId);
  multiple.value = !selection.length;
}
};
/** æŽ’序触发事件 */
const handleSortChange = (column: any) => {
  queryParams.value.orderByColumn = column.prop;
  queryParams.value.isAsc = column.order;
  getList();
}
};
/** è¯¦ç»†æŒ‰é’®æ“ä½œ */
const handleView = (row: OperLogVO) => {
  dialog.visible = true;
  form.value = row;
}
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: OperLogVO) => {
  const operIds = row?.operId || ids.value;
  await proxy?.$modal.confirm('是否确认删除日志编号为"' + operIds + '"的数据项?');
  await delOperlog(operIds);
  await getList();
  proxy?.$modal.msgSuccess("删除成功");
}
  proxy?.$modal.msgSuccess('删除成功');
};
/** æ¸…空按钮操作 */
const handleClean = async () => {
  await proxy?.$modal.confirm("是否确认清空所有操作日志数据项?");
  await proxy?.$modal.confirm('是否确认清空所有操作日志数据项?');
  await cleanOperlog();
  await getList();
  proxy?.$modal.msgSuccess("清空成功");
}
  proxy?.$modal.msgSuccess('清空成功');
};
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
  proxy?.download("monitor/operlog/export", {
    ...queryParams.value,
  }, `config_${new Date().getTime()}.xlsx`);
}
  proxy?.download(
    'monitor/operlog/export',
    {
      ...queryParams.value
    },
    `config_${new Date().getTime()}.xlsx`
  );
};
onMounted(() => {
  getList();
})
});
</script>
src/views/redirect/index.vue
@@ -3,12 +3,12 @@
</template>
<script setup>
import { useRoute, useRouter } from 'vue-router'
import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();
const { params, query } = route
const { path } = params
const { params, query } = route;
const { path } = params;
router.replace({ path: '/' + path, query })
router.replace({ path: '/' + path, query });
</script>
src/views/register.vue
@@ -2,7 +2,7 @@
  <div class="register">
    <el-form ref="registerRef" :model="registerForm" :rules="registerRules" class="register-form">
      <h3 class="title">RuoYi-Vue-Plus多租户管理系统</h3>
      <el-form-item prop="tenantId" v-if="tenantEnabled">
      <el-form-item v-if="tenantEnabled" prop="tenantId">
        <el-select v-model="registerForm.tenantId" filterable placeholder="请选择/输入公司名称" style="width: 100%">
          <el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" :value="item.tenantId"> </el-option>
          <template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template>
@@ -30,20 +30,20 @@
          <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
        </el-input>
      </el-form-item>
      <el-form-item prop="code" v-if="captchaEnabled">
        <el-input size="large" v-model="registerForm.code" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter="handleRegister">
      <el-form-item v-if="captchaEnabled" prop="code">
        <el-input v-model="registerForm.code" size="large" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter="handleRegister">
          <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
        </el-input>
        <div class="register-code">
          <img :src="codeUrl" @click="getCode" class="register-code-img" />
          <img :src="codeUrl" class="register-code-img" @click="getCode" />
        </div>
      </el-form-item>
      <el-form-item style="width:100%;">
        <el-button :loading="loading" size="large" type="primary" style="width:100%;" @click.prevent="handleRegister">
      <el-form-item style="width: 100%">
        <el-button :loading="loading" size="large" type="primary" style="width: 100%" @click.prevent="handleRegister">
          <span v-if="!loading">注 å†Œ</span>
          <span v-else>注 å†Œ ä¸­...</span>
        </el-button>
        <div style="float: right;">
        <div style="float: right">
          <router-link class="link-type" :to="'/login'">使用已有账户登录</router-link>
        </div>
      </el-form-item>
@@ -63,46 +63,43 @@
const router = useRouter();
const registerForm = ref<RegisterForm>({
  tenantId: "",
  username: "",
  password: "",
  confirmPassword: "",
  code: "",
  uuid: "",
  userType: "sys_user"
  tenantId: '',
  username: '',
  password: '',
  confirmPassword: '',
  code: '',
  uuid: '',
  userType: 'sys_user'
});
// ç§Ÿæˆ·å¼€å…³
const tenantEnabled = ref(true);
const equalToPassword = (rule: any, value: string, callback: any) => {
  if (registerForm.value.password !== value) {
    callback(new Error("两次输入的密码不一致"));
    callback(new Error('两次输入的密码不一致'));
  } else {
    callback();
  }
};
const registerRules: ElFormRules = {
  tenantId: [
    { required: true, trigger: "blur", message: "请输入您的租户编号" }
  ],
  tenantId: [{ required: true, trigger: 'blur', message: '请输入您的租户编号' }],
  username: [
    { required: true, trigger: "blur", message: "请输入您的账号" },
    { min: 2, max: 20, message: "用户账号长度必须介于 2 å’Œ 20 ä¹‹é—´", trigger: "blur" }
    { required: true, trigger: 'blur', message: '请输入您的账号' },
    { min: 2, max: 20, message: '用户账号长度必须介于 2 å’Œ 20 ä¹‹é—´', trigger: 'blur' }
  ],
  password: [
    { required: true, trigger: "blur", message: "请输入您的密码" },
    { min: 5, max: 20, message: "用户密码长度必须介于 5 å’Œ 20 ä¹‹é—´", trigger: "blur" }
    { required: true, trigger: 'blur', message: '请输入您的密码' },
    { min: 5, max: 20, message: '用户密码长度必须介于 5 å’Œ 20 ä¹‹é—´', trigger: 'blur' }
  ],
  confirmPassword: [
    { required: true, trigger: "blur", message: "请再次输入您的密码" },
    { required: true, validator: equalToPassword, trigger: "blur" }
    { required: true, trigger: 'blur', message: '请再次输入您的密码' },
    { required: true, validator: equalToPassword, trigger: 'blur' }
  ],
  code: [{ required: true, trigger: "change", message: "请输入验证码" }]
  code: [{ required: true, trigger: 'change', message: '请输入验证码' }]
};
const codeUrl = ref("");
const codeUrl = ref('');
const loading = ref(false);
const captchaEnabled = ref(true);
const registerRef = ref<ElFormInstance>();
@@ -116,11 +113,11 @@
      const [err] = await to(register(registerForm.value));
      if (!err) {
        const username = registerForm.value.username;
        await ElMessageBox.alert("<font color='red'>恭喜你,您的账号 " + username + " æ³¨å†ŒæˆåŠŸï¼</font>", "系统提示", {
        await ElMessageBox.alert("<font color='red'>恭喜你,您的账号 " + username + ' æ³¨å†ŒæˆåŠŸï¼</font>', '系统提示', {
          dangerouslyUseHTMLString: true,
          type: "success",
          type: 'success'
        });
        await router.push("/login");
        await router.push('/login');
      } else {
        loading.value = false;
        if (captchaEnabled) {
@@ -129,7 +126,7 @@
      }
    }
  });
}
};
const getCode = async () => {
  const res = await getCodeImg();
@@ -150,12 +147,12 @@
      registerForm.value.tenantId = tenantList.value[0].tenantId;
    }
  }
}
};
onMounted(() => {
  getCode();
  initTenantList();
})
});
</script>
<style lang="scss" scoped>
@@ -164,7 +161,7 @@
  justify-content: center;
  align-items: center;
  height: 100%;
  background-image: url("../assets/images/login-background.jpg");
  background-image: url('../assets/images/login-background.jpg');
  background-size: cover;
}
src/views/system/client/index.vue
@@ -1,8 +1,8 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="search" v-show="showSearch">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="100px">
      <div v-show="showSearch" class="search">
        <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="100px">
          <el-form-item label="客户端key" prop="clientKey">
            <el-input v-model="queryParams.clientKey" placeholder="请输入客户端key" clearable style="width: 240px" @keyup.enter="handleQuery" />
          </el-form-item>
@@ -26,28 +26,28 @@
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:client:add']">新增</el-button>
            <el-button v-hasPermi="['system:client:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:client:edit']">
            <el-button v-hasPermi="['system:client:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
              ä¿®æ”¹
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:client:remove']">
            <el-button v-hasPermi="['system:client:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
              åˆ é™¤
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:client:export']">导出</el-button>
            <el-button v-hasPermi="['system:client:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="clientList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="id" align="center" prop="id" v-if="true" />
        <el-table-column v-if="true" label="id" align="center" prop="id" />
        <el-table-column label="客户端id" align="center" prop="clientId" />
        <el-table-column label="客户端key" align="center" prop="clientKey" />
        <el-table-column label="客户端秘钥" align="center" prop="clientSecret" />
@@ -63,7 +63,7 @@
        </el-table-column>
        <el-table-column label="Token活跃超时时间" align="center" prop="activeTimeout" />
        <el-table-column label="Token固定超时时间" align="center" prop="timeout" />
        <el-table-column label="状态" align="center" key="status">
        <el-table-column key="status" label="状态" align="center">
          <template #default="scope">
            <el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
          </template>
@@ -71,19 +71,19 @@
        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:client:edit']"></el-button>
              <el-button v-hasPermi="['system:client:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:client:remove']"></el-button>
              <el-button v-hasPermi="['system:client:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total>0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹å®¢æˆ·ç«¯ç®¡ç†å¯¹è¯æ¡† -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
      <el-form ref="clientFormRef" :model="form" :rules="rules" label-width="100px">
        <el-form-item label="客户端key" prop="clientKey">
          <el-input v-model="form.clientKey" :disabled="form.id != null" placeholder="请输入客户端key" />
@@ -146,9 +146,9 @@
import { ClientVO, ClientQuery, ClientForm } from '@/api/system/client/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict("sys_normal_disable"));
const { sys_grant_type } = toRefs<any>(proxy?.useDict("sys_grant_type"));
const { sys_device_type } = toRefs<any>(proxy?.useDict("sys_device_type"));
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
const { sys_grant_type } = toRefs<any>(proxy?.useDict('sys_grant_type'));
const { sys_device_type } = toRefs<any>(proxy?.useDict('sys_device_type'));
const clientList = ref<ClientVO[]>([]);
const buttonLoading = ref(false);
@@ -176,10 +176,10 @@
  deviceType: undefined,
  activeTimeout: undefined,
  timeout: undefined,
  status: undefined,
}
  status: undefined
};
const data = reactive<PageData<ClientForm, ClientQuery>>({
  form: {...initFormData},
  form: { ...initFormData },
  queryParams: {
    pageNum: 1,
    pageSize: 10,
@@ -190,27 +190,15 @@
    deviceType: undefined,
    activeTimeout: undefined,
    timeout: undefined,
    status: undefined,
    status: undefined
  },
  rules: {
    id: [
      { required: true, message: "id不能为空", trigger: "blur" }
    ],
    clientId: [
      { required: true, message: "客户端id不能为空", trigger: "blur" }
    ],
    clientKey: [
      { required: true, message: "客户端key不能为空", trigger: "blur" }
    ],
    clientSecret: [
      { required: true, message: "客户端秘钥不能为空", trigger: "blur" }
    ],
    grantTypeList: [
      { required: true, message: "授权类型不能为空", trigger: "change" }
    ],
    deviceType: [
      { required: true, message: "设备类型不能为空", trigger: "change" }
    ],
    id: [{ required: true, message: 'id不能为空', trigger: 'blur' }],
    clientId: [{ required: true, message: '客户端id不能为空', trigger: 'blur' }],
    clientKey: [{ required: true, message: '客户端key不能为空', trigger: 'blur' }],
    clientSecret: [{ required: true, message: '客户端秘钥不能为空', trigger: 'blur' }],
    grantTypeList: [{ required: true, message: '授权类型不能为空', trigger: 'change' }],
    deviceType: [{ required: true, message: '设备类型不能为空', trigger: 'change' }]
  }
});
@@ -223,55 +211,55 @@
  clientList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
};
/** å–消按钮 */
const cancel = () => {
  reset();
  dialog.visible = false;
}
};
/** è¡¨å•重置 */
const reset = () => {
  form.value = {...initFormData};
  form.value = { ...initFormData };
  clientFormRef.value?.resetFields();
}
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value?.resetFields();
  handleQuery();
}
};
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: ClientVO[]) => {
  ids.value = selection.map(item => item.id);
  ids.value = selection.map((item) => item.id);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
};
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
  reset();
  dialog.visible = true;
  dialog.title = "添加客户端管理";
}
  dialog.title = '添加客户端管理';
};
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = async (row?: ClientVO) => {
  reset();
  const _id = row?.id || ids.value[0]
  const _id = row?.id || ids.value[0];
  const res = await getClient(_id);
  Object.assign(form.value, res.data);
  dialog.visible = true;
  dialog.title = "修改客户端管理";
}
  dialog.title = '修改客户端管理';
};
/** æäº¤æŒ‰é’® */
const submitForm = () => {
@@ -279,44 +267,48 @@
    if (valid) {
      buttonLoading.value = true;
      if (form.value.id) {
        await updateClient(form.value).finally(() =>  buttonLoading.value = false);
        await updateClient(form.value).finally(() => (buttonLoading.value = false));
      } else {
        await addClient(form.value).finally(() =>  buttonLoading.value = false);
        await addClient(form.value).finally(() => (buttonLoading.value = false));
      }
      proxy?.$modal.msgSuccess("修改成功");
      proxy?.$modal.msgSuccess('修改成功');
      dialog.visible = false;
      await getList();
    }
  });
}
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: ClientVO) => {
  const _ids = row?.id || ids.value;
  await proxy?.$modal.confirm('是否确认删除客户端管理编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
  await proxy?.$modal.confirm('是否确认删除客户端管理编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
  await delClient(_ids);
  proxy?.$modal.msgSuccess("删除成功");
  proxy?.$modal.msgSuccess('删除成功');
  await getList();
}
};
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
  proxy?.download('system/client/export', {
    ...queryParams.value
  }, `client_${new Date().getTime()}.xlsx`)
}
  proxy?.download(
    'system/client/export',
    {
      ...queryParams.value
    },
    `client_${new Date().getTime()}.xlsx`
  );
};
/** çŠ¶æ€ä¿®æ”¹  */
const handleStatusChange = async (row: ClientVO) => {
  let text = row.status === "0" ? "启用" : "停用"
  let text = row.status === '0' ? '启用' : '停用';
  try {
    await proxy?.$modal.confirm('确认要"' + text + '"吗?');
    await changeStatus(row.id, row.status);
    proxy?.$modal.msgSuccess(text + "成功");
    proxy?.$modal.msgSuccess(text + '成功');
  } catch (err) {
    row.status = row.status === "0" ? "1" : "0";
    row.status = row.status === '0' ? '1' : '0';
  }
}
};
onMounted(() => {
  getList();
src/views/system/config/index.vue
@@ -1,9 +1,9 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="mb-[10px]" v-show="showSearch">
      <div v-show="showSearch" class="mb-[10px]">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
            <el-form-item label="参数名称" prop="configName">
              <el-input v-model="queryParams.configName" placeholder="请输入参数名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
            </el-form-item>
@@ -15,7 +15,7 @@
                <el-option v-for="dict in sys_yes_no" :key="dict.value" :label="dict.label" :value="dict.value" />
              </el-select>
            </el-form-item>
            <el-form-item label="创建时间" style="width: 308px;">
            <el-form-item label="创建时间" style="width: 308px">
              <el-date-picker
                v-model="dateRange"
                value-format="YYYY-MM-DD HH:mm:ss"
@@ -38,31 +38,31 @@
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:config:add']">新增</el-button>
            <el-button v-hasPermi="['system:config:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:config:edit']">
            <el-button v-hasPermi="['system:config:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
              ä¿®æ”¹
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:config:remove']">
            <el-button v-hasPermi="['system:config:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
              åˆ é™¤
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:config:export']">导出</el-button>
            <el-button v-hasPermi="['system:config:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Refresh" @click="handleRefreshCache" v-hasPermi="['system:config:remove']">刷新缓存</el-button>
            <el-button v-hasPermi="['system:config:remove']" type="danger" plain icon="Refresh" @click="handleRefreshCache">刷新缓存</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="configList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="参数主键" align="center" prop="configId" v-if="false" />
        <el-table-column v-if="false" label="参数主键" align="center" prop="configId" />
        <el-table-column label="参数名称" align="center" prop="configName" :show-overflow-tooltip="true" />
        <el-table-column label="参数键名" align="center" prop="configKey" :show-overflow-tooltip="true" />
        <el-table-column label="参数键值" align="center" prop="configValue" :show-overflow-tooltip="true" />
@@ -80,19 +80,19 @@
        <el-table-column label="操作" align="center" width="150" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:config:edit']"></el-button>
              <el-button v-hasPermi="['system:config:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:config:remove']"></el-button>
              <el-button v-hasPermi="['system:config:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹å‚æ•°é…ç½®å¯¹è¯æ¡† -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
      <el-form ref="configFormRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="参数名称" prop="configName">
          <el-input v-model="form.configName" placeholder="请输入参数名称" />
@@ -123,11 +123,11 @@
</template>
<script setup name="Config" lang="ts">
import { listConfig, getConfig, delConfig, addConfig, updateConfig, refreshCache } from "@/api/system/config";
import { ConfigForm, ConfigQuery, ConfigVO } from "@/api/system/config/types";
import { listConfig, getConfig, delConfig, addConfig, updateConfig, refreshCache } from '@/api/system/config';
import { ConfigForm, ConfigQuery, ConfigVO } from '@/api/system/config/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_yes_no } = toRefs<any>(proxy?.useDict("sys_yes_no"));
const { sys_yes_no } = toRefs<any>(proxy?.useDict('sys_yes_no'));
const configList = ref<ConfigVO[]>([]);
const loading = ref(true);
@@ -149,9 +149,9 @@
  configName: '',
  configKey: '',
  configValue: '',
  configType: "Y",
  configType: 'Y',
  remark: ''
}
};
const data = reactive<PageData<ConfigForm, ConfigQuery>>({
  form: { ...initFormData },
  queryParams: {
@@ -159,12 +159,12 @@
    pageSize: 10,
    configName: '',
    configKey: '',
    configType: '',
    configType: ''
  },
  rules: {
    configName: [{ required: true, message: "参数名称不能为空", trigger: "blur" }],
    configKey: [{ required: true, message: "参数键名不能为空", trigger: "blur" }],
    configValue: [{ required: true, message: "参数键值不能为空", trigger: "blur" }]
    configName: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }],
    configKey: [{ required: true, message: '参数键名不能为空', trigger: 'blur' }],
    configValue: [{ required: true, message: '参数键值不能为空', trigger: 'blur' }]
  }
});
@@ -177,40 +177,40 @@
  configList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
};
/** å–消按钮 */
const cancel = () => {
  reset();
  dialog.visible = false;
}
};
/** è¡¨å•重置 */
const reset = () => {
  form.value = { ...initFormData };
  configFormRef.value?.resetFields();
}
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  dateRange.value = ['', ''];
  queryFormRef.value?.resetFields();
  handleQuery();
}
};
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: ConfigVO[]) => {
  ids.value = selection.map(item => item.configId);
  ids.value = selection.map((item) => item.configId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
};
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
  reset();
  dialog.visible = true;
  dialog.title = "添加参数";
}
  dialog.title = '添加参数';
};
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = async (row?: ConfigVO) => {
  reset();
@@ -218,40 +218,44 @@
  const res = await getConfig(configId);
  Object.assign(form.value, res.data);
  dialog.visible = true;
  dialog.title = "修改参数";
}
  dialog.title = '修改参数';
};
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  configFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      form.value.configId ? await updateConfig(form.value) : await addConfig(form.value);
      proxy?.$modal.msgSuccess("操作成功");
      proxy?.$modal.msgSuccess('操作成功');
      dialog.visible = false;
      await getList();
    }
  });
}
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: ConfigVO) => {
  const configIds = row?.configId || ids.value;
  await proxy?.$modal.confirm('是否确认删除参数编号为"' + configIds + '"的数据项?');
  await delConfig(configIds);
  await getList();
  proxy?.$modal.msgSuccess("删除成功");
}
  proxy?.$modal.msgSuccess('删除成功');
};
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
  proxy?.download("system/config/export", {
    ...queryParams.value
  }, `config_${new Date().getTime()}.xlsx`);
}
  proxy?.download(
    'system/config/export',
    {
      ...queryParams.value
    },
    `config_${new Date().getTime()}.xlsx`
  );
};
/** åˆ·æ–°ç¼“存按钮操作 */
const handleRefreshCache = async () => {
  await refreshCache();
  proxy?.$modal.msgSuccess("刷新缓存成功");
}
  proxy?.$modal.msgSuccess('刷新缓存成功');
};
onMounted(() => {
  getList();
})
});
</script>
src/views/system/dept/index.vue
@@ -1,7 +1,7 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="mb-[10px]" v-show="showSearch">
      <div v-show="showSearch" class="mb-[10px]">
        <el-card shadow="hover">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
            <el-form-item label="部门名称" prop="deptName">
@@ -25,21 +25,21 @@
      <template #header>
        <el-row :gutter="10">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['system:dept:add']">新增 </el-button>
            <el-button v-hasPermi="['system:dept:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增 </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table
        ref="deptTableRef"
        v-loading="loading"
        :data="deptList"
        row-key="deptId"
        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
        ref="deptTableRef"
        :default-expand-all="isExpandAll"
      >
        <el-table-column prop="deptName" label="部门名称" width="260"></el-table-column>
@@ -57,23 +57,23 @@
        <el-table-column fixed="right" align="center" label="操作">
          <template #default="scope">
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dept:edit']" />
              <el-button v-hasPermi="['system:dept:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)" />
            </el-tooltip>
            <el-tooltip content="新增" placement="top">
              <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['system:dept:add']" />
              <el-button v-hasPermi="['system:dept:add']" link type="primary" icon="Plus" @click="handleAdd(scope.row)" />
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dept:remove']" />
              <el-button v-hasPermi="['system:dept:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)" />
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
    <el-dialog :title="dialog.title" v-model="dialog.visible" destroy-on-close append-to-bod width="600px">
    <el-dialog v-model="dialog.visible" :title="dialog.title" destroy-on-close append-to-bod width="600px">
      <el-form ref="deptFormRef" :model="form" :rules="rules" label-width="80px">
        <el-row>
          <el-col :span="24" v-if="form.parentId !== 0">
          <el-col v-if="form.parentId !== 0" :span="24">
            <el-form-item label="上级部门" prop="parentId">
              <el-tree-select
                v-model="form.parentId"
@@ -115,8 +115,7 @@
          <el-col :span="12">
            <el-form-item label="部门状态">
              <el-radio-group v-model="form.status">
                <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">{{ dict.label
                }}</el-radio>
                <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
@@ -133,26 +132,25 @@
</template>
<script setup name="Dept" lang="ts">
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from "@/api/system/dept"
import { DeptForm, DeptQuery, DeptVO } from "@/api/system/dept/types";
import {UserVO} from "@/api/system/user/types";
import {listUserByDeptId} from "@/api/system/user";
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from '@/api/system/dept';
import { DeptForm, DeptQuery, DeptVO } from '@/api/system/dept/types';
import { UserVO } from '@/api/system/user/types';
import { listUserByDeptId } from '@/api/system/user';
interface DeptOptionsType {
  deptId: number | string;
  deptName: string;
  children: DeptOptionsType[];
}
const { proxy } = getCurrentInstance() as ComponentInternalInstance
const { sys_normal_disable } = toRefs<any>(proxy?.useDict("sys_normal_disable"));
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
const deptList = ref<DeptVO[]>([])
const loading = ref(true)
const showSearch = ref(true)
const deptOptions = ref<DeptOptionsType[]>([])
const isExpandAll = ref(true)
const deptList = ref<DeptVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const deptOptions = ref<DeptOptionsType[]>([]);
const isExpandAll = ref(true);
const deptUserList = ref<UserVO[]>([]);
const dialog = reactive<DialogOption>({
@@ -172,8 +170,8 @@
  leader: undefined,
  phone: undefined,
  email: undefined,
  status: "0"
}
  status: '0'
};
const data = reactive<PageData<DeptForm, DeptQuery>>({
  form: { ...initFormData },
  queryParams: {
@@ -183,30 +181,30 @@
    status: undefined
  },
  rules: {
    parentId: [{ required: true, message: "上级部门不能为空", trigger: "blur" }],
    deptName: [{ required: true, message: "部门名称不能为空", trigger: "blur" }],
    orderNum: [{ required: true, message: "显示排序不能为空", trigger: "blur" }],
    email: [{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }],
    phone: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }]
  },
})
    parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }],
    deptName: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }],
    orderNum: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }],
    email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
    phone: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }]
  }
});
const { queryParams, form, rules } = toRefs<PageData<DeptForm, DeptQuery>>(data)
const { queryParams, form, rules } = toRefs<PageData<DeptForm, DeptQuery>>(data);
/** æŸ¥è¯¢èœå•列表 */
const getList = async () => {
  loading.value = true;
  const res = await listDept(queryParams.value);
  const data = proxy?.handleTree<DeptVO>(res.data, "deptId")
  const data = proxy?.handleTree<DeptVO>(res.data, 'deptId');
  if (data) {
    deptList.value = data
    deptList.value = data;
  }
  loading.value = false
}
  loading.value = false;
};
/** æŸ¥è¯¢å½“前部门的所有用户 */
async function getDeptAllUser(deptId: any) {
  if (deptId !== null && deptId !== "" && deptId !== undefined) {
  if (deptId !== null && deptId !== '' && deptId !== undefined) {
    const res = await listUserByDeptId(deptId);
    deptUserList.value = res.data;
  }
@@ -214,52 +212,52 @@
/** å–消按钮 */
const cancel = () => {
  reset()
  dialog.visible = false
}
  reset();
  dialog.visible = false;
};
/** è¡¨å•重置 */
const reset = () => {
  form.value = { ...initFormData };
  deptFormRef.value?.resetFields();
}
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  getList();
}
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value?.resetFields();
  handleQuery()
}
  handleQuery();
};
/** å±•å¼€/折叠操作 */
const handleToggleExpandAll = () => {
  isExpandAll.value = !isExpandAll.value;
  toggleExpandAll(deptList.value, isExpandAll.value)
}
  toggleExpandAll(deptList.value, isExpandAll.value);
};
/** å±•å¼€/折叠所有 */
const toggleExpandAll = (data: DeptVO[], status: boolean) => {
  data.forEach((item) => {
    deptTableRef.value?.toggleRowExpansion(item, status)
    if (item.children && item.children.length > 0) toggleExpandAll(item.children, status)
  })
}
    deptTableRef.value?.toggleRowExpansion(item, status);
    if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
  });
};
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = async (row?: DeptVO) => {
  reset();
  const res = await listDept();
  const data = proxy?.handleTree<DeptOptionsType>(res.data, "deptId");
  const data = proxy?.handleTree<DeptOptionsType>(res.data, 'deptId');
  if (data) {
    deptOptions.value = data
    deptOptions.value = data;
    if (row && row.deptId) {
      form.value.parentId = row?.deptId;
    }
    dialog.visible = true;
    dialog.title = "添加部门";
    dialog.title = '添加部门';
  }
}
};
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = async (row: DeptVO) => {
@@ -267,9 +265,9 @@
  //查询当前部门所有用户
  getDeptAllUser(row.deptId);
  const res = await getDept(row.deptId);
  form.value = res.data
  form.value = res.data;
  const response = await listDeptExcludeChild(row.deptId);
  const data = proxy?.handleTree<DeptOptionsType>(response.data, "deptId")
  const data = proxy?.handleTree<DeptOptionsType>(response.data, 'deptId');
  if (data) {
    deptOptions.value = data;
    if (data.length === 0) {
@@ -282,26 +280,26 @@
    }
  }
  dialog.visible = true;
  dialog.title = "修改部门";
}
  dialog.title = '修改部门';
};
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  deptFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      form.value.deptId ? await updateDept(form.value) : await addDept(form.value);
      proxy?.$modal.msgSuccess("操作成功");
      proxy?.$modal.msgSuccess('操作成功');
      dialog.visible = false;
      await getList();
    }
  })
}
  });
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row: DeptVO) => {
  await proxy?.$modal.confirm('是否确认删除名称为"' + row.deptName + '"的数据项?');
  await delDept(row.deptId);
  await getList();
  proxy?.$modal.msgSuccess("删除成功");
}
  proxy?.$modal.msgSuccess('删除成功');
};
onMounted(() => {
  getList();
src/views/system/dict/data.vue
@@ -1,9 +1,9 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="mb-[10px]" v-show="showSearch">
      <div v-show="showSearch" class="mb-[10px]">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
            <el-form-item label="字典名称" prop="dictType">
              <el-select v-model="queryParams.dictType" style="width: 200px">
                <el-option v-for="item in typeOptions" :key="item.dictId" :label="item.dictName" :value="item.dictType" />
@@ -24,29 +24,29 @@
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:dict:add']">新增</el-button>
            <el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:dict:edit']">修改</el-button>
            <el-button v-hasPermi="['system:dict:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:dict:remove']">
            <el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
              åˆ é™¤
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:dict:export']">导出</el-button>
            <el-button v-hasPermi="['system:dict:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Close" @click="handleClose">关闭</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="字典编码" align="center" prop="dictCode" v-if="false" />
        <el-table-column v-if="false" label="字典编码" align="center" prop="dictCode" />
        <el-table-column label="字典标签" align="center" prop="dictLabel">
          <template #default="scope">
            <span v-if="(scope.row.listClass === '' || scope.row.listClass === 'default') && (scope.row.cssClass === '' || scope.row.cssClass == null)">{{ scope.row.dictLabel }}</span>
@@ -64,19 +64,19 @@
        <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dict:edit']"></el-button>
              <el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dict:remove']"></el-button>
              <el-button v-hasPermi="['system:dict:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹å‚æ•°é…ç½®å¯¹è¯æ¡† -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
      <el-form ref="dataFormRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="字典类型">
          <el-input v-model="form.dictType" :disabled="true" />
@@ -118,13 +118,13 @@
</template>
<script setup name="Data" lang="ts">
import useDictStore from '@/store/modules/dict'
import { optionselect as getDictOptionselect, getType } from "@/api/system/dict/type";
import { listData, getData, delData, addData, updateData } from "@/api/system/dict/data";
import useDictStore from '@/store/modules/dict';
import { optionselect as getDictOptionselect, getType } from '@/api/system/dict/type';
import { listData, getData, delData, addData, updateData } from '@/api/system/dict/data';
import { DictTypeVO } from '@/api/system/dict/type/types';
import { DictDataForm, DictDataQuery, DictDataVO } from "@/api/system/dict/data/types";
import { DictDataForm, DictDataQuery, DictDataVO } from '@/api/system/dict/data/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const route = useRoute();
const dataList = ref<DictDataVO[]>([]);
@@ -134,12 +134,11 @@
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const defaultDictType = ref("");
const defaultDictType = ref('');
const typeOptions = ref<DictTypeVO[]>([]);
const dataFormRef = ref<ElFormInstance>();
const queryFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
  visible: false,
@@ -147,13 +146,13 @@
});
// æ•°æ®æ ‡ç­¾å›žæ˜¾æ ·å¼
const listClassOptions = ref<Array<{ value: string, label: string }>>([
  { value: "default", label: "默认" },
  { value: "primary", label: "主要" },
  { value: "success", label: "成功" },
  { value: "info", label: "信息" },
  { value: "warning", label: "警告" },
  { value: "danger", label: "危险" }
const listClassOptions = ref<Array<{ value: string; label: string }>>([
  { value: 'default', label: '默认' },
  { value: 'primary', label: '主要' },
  { value: 'success', label: '成功' },
  { value: 'info', label: '信息' },
  { value: 'warning', label: '警告' },
  { value: 'danger', label: '危险' }
]);
const initFormData: DictDataForm = {
@@ -161,10 +160,10 @@
  dictLabel: '',
  dictValue: '',
  cssClass: '',
  listClass: "default",
  listClass: 'default',
  dictSort: 0,
  remark: ''
}
};
const data = reactive<PageData<DictDataForm, DictDataQuery>>({
  form: { ...initFormData },
  queryParams: {
@@ -175,9 +174,9 @@
    dictLabel: ''
  },
  rules: {
    dictLabel: [{ required: true, message: "数据标签不能为空", trigger: "blur" }],
    dictValue: [{ required: true, message: "数据键值不能为空", trigger: "blur" }],
    dictSort: [{ required: true, message: "数据顺序不能为空", trigger: "blur" }]
    dictLabel: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }],
    dictValue: [{ required: true, message: '数据键值不能为空', trigger: 'blur' }],
    dictSort: [{ required: true, message: '数据顺序不能为空', trigger: 'blur' }]
  }
});
@@ -189,13 +188,13 @@
  queryParams.value.dictType = data.dictType;
  defaultDictType.value = data.dictType;
  getList();
}
};
/** æŸ¥è¯¢å­—典类型列表 */
const getTypeList = async () => {
  const res = await getDictOptionselect()
  const res = await getDictOptionselect();
  typeOptions.value = res.data;
}
};
/** æŸ¥è¯¢å­—典数据列表 */
const getList = async () => {
  loading.value = true;
@@ -203,46 +202,46 @@
  dataList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
};
/** å–消按钮 */
const cancel = () => {
  dialog.visible = false;
  reset();
}
};
/** è¡¨å•重置 */
const reset = () => {
  form.value = { ...initFormData };
  dataFormRef.value?.resetFields();
}
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
};
/** è¿”回按钮操作 */
const handleClose = () => {
  const obj = { path: "/system/dict" };
  const obj = { path: '/system/dict' };
  proxy?.$tab.closeOpenPage(obj);
}
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value?.resetFields();
  queryParams.value.dictType = defaultDictType.value;
  handleQuery();
}
};
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
  reset();
  form.value.dictType = queryParams.value.dictType;
  dialog.visible = true;
  dialog.title = "添加字典数据";
}
  dialog.title = '添加字典数据';
};
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: DictDataVO[]) => {
  ids.value = selection.map(item => item.dictCode);
  ids.value = selection.map((item) => item.dictCode);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
};
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = async (row?: DictDataVO) => {
  reset();
@@ -250,40 +249,42 @@
  const res = await getData(dictCode);
  Object.assign(form.value, res.data);
  dialog.visible = true;
  dialog.title = "修改字典数据";
}
  dialog.title = '修改字典数据';
};
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  dataFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      form.value.dictCode ? await updateData(form.value) : await addData(form.value);
      useDictStore().removeDict(queryParams.value.dictType);
      proxy?.$modal.msgSuccess("操作成功");
      proxy?.$modal.msgSuccess('操作成功');
      dialog.visible = false;
      await getList();
    }
  });
}
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: DictDataVO) => {
  const dictCodes = row?.dictCode || ids.value;
  await proxy?.$modal.confirm('是否确认删除字典编码为"' + dictCodes + '"的数据项?');
  await delData(dictCodes);
  await getList();
  proxy?.$modal.msgSuccess("删除成功");
  proxy?.$modal.msgSuccess('删除成功');
  useDictStore().removeDict(queryParams.value.dictType);
}
};
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
  proxy?.download("system/dict/data/export", {
    ...queryParams.value
  }, `dict_data_${new Date().getTime()}.xlsx`);
}
  proxy?.download(
    'system/dict/data/export',
    {
      ...queryParams.value
    },
    `dict_data_${new Date().getTime()}.xlsx`
  );
};
onMounted(() => {
  getTypes(route.params && route.params.dictId as string);
  getTypes(route.params && (route.params.dictId as string));
  getTypeList();
})
});
</script>
src/views/system/dict/index.vue
@@ -1,9 +1,9 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="mb-[10px]" v-show="showSearch">
      <div v-show="showSearch" class="mb-[10px]">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
            <el-form-item label="字典名称" prop="dictName">
              <el-input v-model="queryParams.dictName" placeholder="请输入字典名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
            </el-form-item>
@@ -33,29 +33,29 @@
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:dict:add']">新增</el-button>
            <el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:dict:edit']">修改</el-button>
            <el-button v-hasPermi="['system:dict:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:dict:remove']">
            <el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
              åˆ é™¤
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:dict:export']">导出</el-button>
            <el-button v-hasPermi="['system:dict:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Refresh" @click="handleRefreshCache" v-hasPermi="['system:dict:remove']">刷新缓存</el-button>
            <el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Refresh" @click="handleRefreshCache">刷新缓存</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="typeList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="字典编号" align="center" prop="dictId" v-if="false" />
        <el-table-column v-if="false" label="字典编号" align="center" prop="dictId" />
        <el-table-column label="字典名称" align="center" prop="dictName" :show-overflow-tooltip="true" />
        <el-table-column label="字典类型" align="center" :show-overflow-tooltip="true">
          <template #default="scope">
@@ -73,19 +73,19 @@
        <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dict:edit']"></el-button>
              <el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dict:remove']"></el-button>
              <el-button v-hasPermi="['system:dict:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹å‚æ•°é…ç½®å¯¹è¯æ¡† -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
      <el-form ref="dictFormRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="字典名称" prop="dictName">
          <el-input v-model="form.dictName" placeholder="请输入字典名称" />
@@ -108,9 +108,9 @@
</template>
<script setup name="Dict" lang="ts">
import useDictStore from '@/store/modules/dict'
import { listType, getType, delType, addType, updateType, refreshCache } from "@/api/system/dict/type";
import { DictTypeForm, DictTypeQuery, DictTypeVO } from "@/api/system/dict/type/types";
import useDictStore from '@/store/modules/dict';
import { listType, getType, delType, addType, updateType, refreshCache } from '@/api/system/dict/type';
import { DictTypeForm, DictTypeQuery, DictTypeVO } from '@/api/system/dict/type/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -126,7 +126,6 @@
const dictFormRef = ref<ElFormInstance>();
const queryFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
  visible: false,
  title: ''
@@ -137,7 +136,7 @@
  dictName: '',
  dictType: '',
  remark: ''
}
};
const data = reactive<PageData<DictTypeForm, DictTypeQuery>>({
  form: { ...initFormData },
  queryParams: {
@@ -147,9 +146,9 @@
    dictType: ''
  },
  rules: {
    dictName: [{ required: true, message: "字典名称不能为空", trigger: "blur" }],
    dictType: [{ required: true, message: "字典类型不能为空", trigger: "blur" }]
  },
    dictName: [{ required: true, message: '字典名称不能为空', trigger: 'blur' }],
    dictType: [{ required: true, message: '字典类型不能为空', trigger: 'blur' }]
  }
});
const { queryParams, form, rules } = toRefs(data);
@@ -157,45 +156,45 @@
/** æŸ¥è¯¢å­—典类型列表 */
const getList = () => {
  loading.value = true;
  listType(proxy?.addDateRange(queryParams.value, dateRange.value)).then(res => {
  listType(proxy?.addDateRange(queryParams.value, dateRange.value)).then((res) => {
    typeList.value = res.rows;
    total.value = res.total;
    loading.value = false;
  });
}
};
/** å–消按钮 */
const cancel = () => {
  reset();
  dialog.visible = false;
}
};
/** è¡¨å•重置 */
const reset = () => {
  form.value = { ...initFormData };
  dictFormRef.value?.resetFields();
}
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  dateRange.value = ['', ''];
  queryFormRef.value?.resetFields();
  handleQuery();
}
};
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
  reset();
  dialog.visible = true;
  dialog.title = "添加字典类型";
}
  dialog.title = '添加字典类型';
};
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: DictTypeVO[]) => {
  ids.value = selection.map(item => item.dictId);
  ids.value = selection.map((item) => item.dictId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
};
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = async (row?: DictTypeVO) => {
  reset();
@@ -203,41 +202,45 @@
  const res = await getType(dictId);
  Object.assign(form.value, res.data);
  dialog.visible = true;
  dialog.title = "修改字典类型";
}
  dialog.title = '修改字典类型';
};
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  dictFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      form.value.dictId ? await updateType(form.value) : await addType(form.value);
      proxy?.$modal.msgSuccess("操作成功");
      proxy?.$modal.msgSuccess('操作成功');
      dialog.visible = false;
      getList();
    }
  });
}
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: DictTypeVO) => {
  const dictIds = row?.dictId || ids.value;
  await proxy?.$modal.confirm('是否确认删除字典编号为"' + dictIds + '"的数据项?');
  await delType(dictIds);
  getList();
  proxy?.$modal.msgSuccess("删除成功");
}
  proxy?.$modal.msgSuccess('删除成功');
};
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
  proxy?.download("system/dict/type/export", {
    ...queryParams.value
  }, `dict_${new Date().getTime()}.xlsx`);
}
  proxy?.download(
    'system/dict/type/export',
    {
      ...queryParams.value
    },
    `dict_${new Date().getTime()}.xlsx`
  );
};
/** åˆ·æ–°ç¼“存按钮操作 */
const handleRefreshCache = async () => {
  await refreshCache();
  proxy?.$modal.msgSuccess("刷新成功");
  proxy?.$modal.msgSuccess('刷新成功');
  useDictStore().cleanDict();
}
};
onMounted(() => {
  getList();
})
});
</script>
src/views/system/menu/index.vue
@@ -1,7 +1,7 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="mb-[10px]" v-show="showSearch">
      <div v-show="showSearch" class="mb-[10px]">
        <el-card shadow="hover">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
            <el-form-item label="菜单名称" prop="menuName">
@@ -25,21 +25,21 @@
      <template #header>
        <el-row :gutter="10">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['system:menu:add']">新增 </el-button>
            <el-button v-hasPermi="['system:menu:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增 </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table
        ref="menuTableRef"
        v-loading="loading"
        :data="menuList"
        row-key="menuId"
        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
        ref="menuTableRef"
        :default-expand-all="isExpandAll"
      >
        <el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true" width="160"></el-table-column>
@@ -64,20 +64,20 @@
        <el-table-column fixed="right" label="操作" width="180">
          <template #default="scope">
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:menu:edit']" />
              <el-button v-hasPermi="['system:menu:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)" />
            </el-tooltip>
            <el-tooltip content="新增" placement="top">
              <el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['system:menu:add']" />
              <el-button v-hasPermi="['system:menu:add']" link type="primary" icon="Plus" @click="handleAdd(scope.row)" />
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:menu:remove']" />
              <el-button v-hasPermi="['system:menu:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)" />
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
    <el-dialog :title="dialog.title" v-model="dialog.visible" destroy-on-close append-to-bod width="750px">
    <el-dialog v-model="dialog.visible" :title="dialog.title" destroy-on-close append-to-bod width="750px">
      <el-form ref="menuFormRef" :model="form" :rules="rules" label-width="100px">
        <el-row>
          <el-col :span="24">
@@ -101,7 +101,7 @@
              </el-radio-group>
            </el-form-item>
          </el-col>
          <el-col :span="24" v-if="form.menuType !== 'F'">
          <el-col v-if="form.menuType !== 'F'" :span="24">
            <el-form-item label="菜单图标" prop="icon">
              <!-- å›¾æ ‡é€‰æ‹©å™¨ -->
              <icon-select v-model="form.icon" />
@@ -117,7 +117,7 @@
              <el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
            </el-form-item>
          </el-col>
          <el-col :span="12" v-if="form.menuType !== 'F'">
          <el-col v-if="form.menuType !== 'F'" :span="12">
            <el-form-item>
              <template #label>
                <span>
@@ -134,7 +134,7 @@
              </el-radio-group>
            </el-form-item>
          </el-col>
          <el-col :span="12" v-if="form.menuType !== 'F'">
          <el-col v-if="form.menuType !== 'F'" :span="12">
            <el-form-item prop="path">
              <template #label>
                <span>
@@ -149,7 +149,7 @@
              <el-input v-model="form.path" placeholder="请输入路由地址" />
            </el-form-item>
          </el-col>
          <el-col :span="12" v-if="form.menuType === 'C'">
          <el-col v-if="form.menuType === 'C'" :span="12">
            <el-form-item prop="component">
              <template #label>
                <span>
@@ -164,7 +164,7 @@
              <el-input v-model="form.component" placeholder="请输入组件路径" />
            </el-form-item>
          </el-col>
          <el-col :span="12" v-if="form.menuType !== 'M'">
          <el-col v-if="form.menuType !== 'M'" :span="12">
            <el-form-item>
              <el-input v-model="form.perms" placeholder="请输入权限标识" maxlength="100" />
              <template #label>
@@ -179,7 +179,7 @@
              </template>
            </el-form-item>
          </el-col>
          <el-col :span="12" v-if="form.menuType === 'C'">
          <el-col v-if="form.menuType === 'C'" :span="12">
            <el-form-item>
              <el-input v-model="form.queryParam" placeholder="请输入路由参数" maxlength="255" />
              <template #label>
@@ -194,7 +194,7 @@
              </template>
            </el-form-item>
          </el-col>
          <el-col :span="12" v-if="form.menuType === 'C'">
          <el-col v-if="form.menuType === 'C'" :span="12">
            <el-form-item>
              <template #label>
                <span>
@@ -212,7 +212,7 @@
              </el-radio-group>
            </el-form-item>
          </el-col>
          <el-col :span="12" v-if="form.menuType !== 'F'">
          <el-col v-if="form.menuType !== 'F'" :span="12">
            <el-form-item>
              <template #label>
                <span>
@@ -271,14 +271,14 @@
  children: MenuOptionsType[] | undefined;
}
const { proxy } = getCurrentInstance() as ComponentInternalInstance
const { sys_show_hide, sys_normal_disable } = toRefs<any>(proxy?.useDict("sys_show_hide", "sys_normal_disable"));
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_show_hide, sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_show_hide', 'sys_normal_disable'));
const menuList = ref<MenuVO[]>([])
const loading = ref(true)
const showSearch = ref(true)
const menuOptions = ref<MenuOptionsType[]>([])
const isExpandAll = ref(false)
const menuList = ref<MenuVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const menuOptions = ref<MenuOptionsType[]>([]);
const isExpandAll = ref(false);
const dialog = reactive<DialogOption>({
  visible: false,
@@ -295,11 +295,11 @@
  icon: '',
  menuType: MenuTypeEnum.M,
  orderNum: 1,
  isFrame: "1",
  isCache: "0",
  visible: "0",
  status: "0"
}
  isFrame: '1',
  isCache: '0',
  visible: '0',
  status: '0'
};
const data = reactive<PageData<MenuForm, MenuQuery>>({
  form: { ...initFormData },
  queryParams: {
@@ -307,73 +307,73 @@
    status: undefined
  },
  rules: {
    menuName: [{ required: true, message: "菜单名称不能为空", trigger: "blur" }],
    orderNum: [{ required: true, message: "菜单顺序不能为空", trigger: "blur" }],
    path: [{ required: true, message: "路由地址不能为空", trigger: "blur" }]
  },
})
    menuName: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }],
    orderNum: [{ required: true, message: '菜单顺序不能为空', trigger: 'blur' }],
    path: [{ required: true, message: '路由地址不能为空', trigger: 'blur' }]
  }
});
const menuTableRef = ref<ElTableInstance>();
const { queryParams, form, rules } = toRefs<PageData<MenuForm, MenuQuery>>(data)
const { queryParams, form, rules } = toRefs<PageData<MenuForm, MenuQuery>>(data);
/** æŸ¥è¯¢èœå•列表 */
const getList = async () => {
  loading.value = true
  loading.value = true;
  const res = await listMenu(queryParams.value);
  const data = proxy?.handleTree<MenuVO>(res.data, "menuId")
  const data = proxy?.handleTree<MenuVO>(res.data, 'menuId');
  if (data) {
    menuList.value = data
    menuList.value = data;
  }
  loading.value = false
}
  loading.value = false;
};
/** æŸ¥è¯¢èœå•下拉树结构 */
const getTreeselect = async () => {
  menuOptions.value = []
  menuOptions.value = [];
  const response = await listMenu();
  const menu: MenuOptionsType = { menuId: 0, menuName: "主类目", children: [] }
  menu.children = proxy?.handleTree<MenuOptionsType>(response.data, "menuId")
  menuOptions.value.push(menu)
}
  const menu: MenuOptionsType = { menuId: 0, menuName: '主类目', children: [] };
  menu.children = proxy?.handleTree<MenuOptionsType>(response.data, 'menuId');
  menuOptions.value.push(menu);
};
/** å–消按钮 */
const cancel = () => {
  reset()
  dialog.visible = false
}
  reset();
  dialog.visible = false;
};
/** è¡¨å•重置 */
const reset = () => {
  form.value = { ...initFormData };
  menuFormRef.value?.resetFields();
}
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  getList();
}
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value?.resetFields();
  handleQuery();
}
};
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = (row?: MenuVO) => {
  reset();
  getTreeselect();
  row && row.menuId ? form.value.parentId = row.menuId : form.value.parentId = 0;
  row && row.menuId ? (form.value.parentId = row.menuId) : (form.value.parentId = 0);
  dialog.visible = true;
  dialog.title = "添加菜单";
}
  dialog.title = '添加菜单';
};
/** å±•å¼€/折叠操作 */
const handleToggleExpandAll = () => {
  isExpandAll.value = !isExpandAll.value;
  toggleExpandAll(menuList.value, isExpandAll.value)
}
  toggleExpandAll(menuList.value, isExpandAll.value);
};
/** å±•å¼€/折叠所有 */
const toggleExpandAll = (data: MenuVO[], status: boolean) => {
  data.forEach((item: MenuVO) => {
    menuTableRef.value?.toggleRowExpansion(item, status)
    if (item.children && item.children.length > 0) toggleExpandAll(item.children, status)
  })
}
    menuTableRef.value?.toggleRowExpansion(item, status);
    if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
  });
};
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = async (row: MenuVO) => {
  reset();
@@ -383,26 +383,26 @@
    form.value = data;
  }
  dialog.visible = true;
  dialog.title = "修改菜单";
}
  dialog.title = '修改菜单';
};
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  menuFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      form.value.menuId ? await updateMenu(form.value) : await addMenu(form.value);
      proxy?.$modal.msgSuccess("操作成功");
      proxy?.$modal.msgSuccess('操作成功');
      dialog.visible = false;
      await getList();
    }
  })
}
  });
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row: MenuVO) => {
  await proxy?.$modal.confirm('是否确认删除名称为"' + row.menuName + '"的数据项?');
  await delMenu(row.menuId);
  await getList();
  proxy?.$modal.msgSuccess("删除成功");
}
  proxy?.$modal.msgSuccess('删除成功');
};
onMounted(() => {
  getList();
src/views/system/notice/index.vue
@@ -1,9 +1,9 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="mb-[10px]" v-show="showSearch">
      <div v-show="showSearch" class="mb-[10px]">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
            <el-form-item label="公告标题" prop="noticeTitle">
              <el-input v-model="queryParams.noticeTitle" placeholder="请输入公告标题" clearable style="width: 200px" @keyup.enter="handleQuery" />
            </el-form-item>
@@ -28,25 +28,25 @@
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:notice:add']">新增</el-button>
            <el-button v-hasPermi="['system:notice:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:notice:edit']"
            <el-button v-hasPermi="['system:notice:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
              >修改</el-button
            >
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:notice:remove']">
            <el-button v-hasPermi="['system:notice:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
              åˆ é™¤
            </el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="noticeList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="序号" align="center" prop="noticeId" width="100" v-if="false" />
        <el-table-column v-if="false" label="序号" align="center" prop="noticeId" width="100" />
        <el-table-column label="公告标题" align="center" prop="noticeTitle" :show-overflow-tooltip="true" />
        <el-table-column label="公告类型" align="center" prop="noticeType" width="100">
          <template #default="scope">
@@ -67,19 +67,19 @@
        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:notice:edit']"></el-button>
              <el-button v-hasPermi="['system:notice:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:notice:remove']"></el-button>
              <el-button v-hasPermi="['system:notice:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹å…¬å‘Šå¯¹è¯æ¡† -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="780px" append-to-body>
    <el-dialog v-model="dialog.visible" :title="dialog.title" width="780px" append-to-body>
      <el-form ref="noticeFormRef" :model="form" :rules="rules" label-width="80px">
        <el-row>
          <el-col :span="12">
@@ -97,8 +97,7 @@
          <el-col :span="24">
            <el-form-item label="状态">
              <el-radio-group v-model="form.status">
                <el-radio v-for="dict in sys_notice_status" :key="dict.value" :label="dict.value">{{ dict.label
                }}</el-radio>
                <el-radio v-for="dict in sys_notice_status" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
@@ -120,11 +119,11 @@
</template>
<script setup name="Notice" lang="ts">
import { listNotice, getNotice, delNotice, addNotice, updateNotice } from "@/api/system/notice";
import { NoticeForm, NoticeQuery, NoticeVO } from "@/api/system/notice/types";
import { listNotice, getNotice, delNotice, addNotice, updateNotice } from '@/api/system/notice';
import { NoticeForm, NoticeQuery, NoticeVO } from '@/api/system/notice/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_notice_status, sys_notice_type } = toRefs<any>(proxy?.useDict("sys_notice_status", "sys_notice_type"));
const { sys_notice_status, sys_notice_type } = toRefs<any>(proxy?.useDict('sys_notice_status', 'sys_notice_type'));
const noticeList = ref<NoticeVO[]>([]);
const loading = ref(true);
@@ -137,7 +136,6 @@
const queryFormRef = ref<ElFormInstance>();
const noticeFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
  visible: false,
  title: ''
@@ -148,10 +146,10 @@
  noticeTitle: '',
  noticeType: '',
  noticeContent: '',
  status: "0",
  status: '0',
  remark: '',
  createByName: ''
}
};
const data = reactive<PageData<NoticeForm, NoticeQuery>>({
  form: { ...initFormData },
  queryParams: {
@@ -163,9 +161,9 @@
    noticeType: ''
  },
  rules: {
    noticeTitle: [{ required: true, message: "公告标题不能为空", trigger: "blur" }],
    noticeType: [{ required: true, message: "公告类型不能为空", trigger: "change" }]
  },
    noticeTitle: [{ required: true, message: '公告标题不能为空', trigger: 'blur' }],
    noticeType: [{ required: true, message: '公告类型不能为空', trigger: 'change' }]
  }
});
const { queryParams, form, rules } = toRefs(data);
@@ -177,39 +175,39 @@
  noticeList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
};
/** å–消按钮 */
const cancel = () => {
  reset();
  dialog.visible = false;
}
};
/** è¡¨å•重置 */
const reset = () => {
  form.value = { ...initFormData };
  noticeFormRef.value?.resetFields();
}
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value?.resetFields();
  handleQuery();
}
};
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: NoticeVO[]) => {
  ids.value = selection.map(item => item.noticeId);
  ids.value = selection.map((item) => item.noticeId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
};
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
  reset();
  dialog.visible = true;
  dialog.title = "添加公告";
}
  dialog.title = '添加公告';
};
/**修改按钮操作 */
const handleUpdate = async (row?: NoticeVO) => {
  reset();
@@ -217,29 +215,29 @@
  const { data } = await getNotice(noticeId);
  Object.assign(form.value, data);
  dialog.visible = true;
  dialog.title = "修改公告";
}
  dialog.title = '修改公告';
};
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  noticeFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      form.value.noticeId ? await updateNotice(form.value) : await addNotice(form.value);
      proxy?.$modal.msgSuccess("修改成功");
      proxy?.$modal.msgSuccess('修改成功');
      dialog.visible = false;
      await getList();
    }
  });
}
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: NoticeVO) => {
  const noticeIds = row?.noticeId || ids.value
  const noticeIds = row?.noticeId || ids.value;
  await proxy?.$modal.confirm('是否确认删除公告编号为"' + noticeIds + '"的数据项?');
  await delNotice(noticeIds);
  await getList();
  proxy?.$modal.msgSuccess("删除成功");
}
  proxy?.$modal.msgSuccess('删除成功');
};
onMounted(() => {
  getList();
})
});
</script>
src/views/system/oss/config.vue
@@ -1,9 +1,9 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="mb-[10px]" v-show="showSearch">
      <div v-show="showSearch" class="mb-[10px]">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
            <el-form-item label="配置key" prop="configKey">
              <el-input v-model="queryParams.configKey" placeholder="配置key" clearable style="width: 200px" @keyup.enter="handleQuery" />
            </el-form-item>
@@ -39,27 +39,27 @@
              åˆ é™¤
            </el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="ossConfigList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="主建" align="center" prop="ossConfigId" v-if="columns[0].visible" />
        <el-table-column label="配置key" align="center" prop="configKey" v-if="columns[1].visible" />
        <el-table-column label="访问站点" align="center" prop="endpoint" v-if="columns[2].visible" width="200" />
        <el-table-column label="自定义域名" align="center" prop="domain" v-if="columns[3].visible" width="200" />
        <el-table-column label="桶名称" align="center" prop="bucketName" v-if="columns[4].visible" />
        <el-table-column label="前缀" align="center" prop="prefix" v-if="columns[5].visible" />
        <el-table-column label="域" align="center" prop="region" v-if="columns[6].visible" />
        <el-table-column label="桶权限类型" align="center" prop="accessPolicy" v-if="columns[7].visible">
        <el-table-column v-if="columns[0].visible" label="主建" align="center" prop="ossConfigId" />
        <el-table-column v-if="columns[1].visible" label="配置key" align="center" prop="configKey" />
        <el-table-column v-if="columns[2].visible" label="访问站点" align="center" prop="endpoint" width="200" />
        <el-table-column v-if="columns[3].visible" label="自定义域名" align="center" prop="domain" width="200" />
        <el-table-column v-if="columns[4].visible" label="桶名称" align="center" prop="bucketName" />
        <el-table-column v-if="columns[5].visible" label="前缀" align="center" prop="prefix" />
        <el-table-column v-if="columns[6].visible" label="域" align="center" prop="region" />
        <el-table-column v-if="columns[7].visible" label="桶权限类型" align="center" prop="accessPolicy">
          <template #default="scope">
            <el-tag type="warning" v-if="scope.row.accessPolicy === '0'">private</el-tag>
            <el-tag type="success" v-if="scope.row.accessPolicy === '1'">public</el-tag>
            <el-tag type="info" v-if="scope.row.accessPolicy === '2'">custom</el-tag>
            <el-tag v-if="scope.row.accessPolicy === '0'" type="warning">private</el-tag>
            <el-tag v-if="scope.row.accessPolicy === '1'" type="success">public</el-tag>
            <el-tag v-if="scope.row.accessPolicy === '2'" type="info">custom</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="是否默认" align="center" prop="status" v-if="columns[8].visible">
        <el-table-column v-if="columns[8].visible" label="是否默认" align="center" prop="status">
          <template #default="scope">
            <el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
          </template>
@@ -76,10 +76,10 @@
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹å¯¹è±¡å­˜å‚¨é…ç½®å¯¹è¯æ¡† -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="800px" append-to-body>
    <el-dialog v-model="dialog.visible" :title="dialog.title" width="800px" append-to-body>
      <el-form ref="ossConfigFormRef" :model="form" :rules="rules" label-width="120px">
        <el-form-item label="配置key" prop="configKey">
          <el-input v-model="form.configKey" placeholder="请输入配置key" />
@@ -132,19 +132,11 @@
</template>
<script setup name="OssConfig" lang="ts">
import {
  listOssConfig,
  getOssConfig,
  delOssConfig,
  addOssConfig,
  updateOssConfig,
  changeOssConfigStatus
} from "@/api/system/ossConfig";
import { OssConfigForm, OssConfigQuery, OssConfigVO } from "@/api/system/ossConfig/types";
import { listOssConfig, getOssConfig, delOssConfig, addOssConfig, updateOssConfig, changeOssConfigStatus } from '@/api/system/ossConfig';
import { OssConfigForm, OssConfigQuery, OssConfigVO } from '@/api/system/ossConfig/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance
const { sys_yes_no } = toRefs<any>(proxy?.useDict("sys_yes_no"));
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_yes_no } = toRefs<any>(proxy?.useDict('sys_yes_no'));
const ossConfigList = ref<OssConfigVO[]>([]);
const buttonLoading = ref(false);
@@ -176,7 +168,6 @@
  { key: 8, label: `状态`, visible: true }
]);
const initFormData: OssConfigForm = {
  ossConfigId: undefined,
  configKey: '',
@@ -186,12 +177,12 @@
  prefix: '',
  endpoint: '',
  domain: '',
  isHttps: "N",
  accessPolicy: "1",
  isHttps: 'N',
  accessPolicy: '1',
  region: '',
  status: "1",
  remark: '',
}
  status: '1',
  remark: ''
};
const data = reactive<PageData<OssConfigForm, OssConfigQuery>>({
  form: { ...initFormData },
  // æŸ¥è¯¢å‚æ•°
@@ -200,47 +191,47 @@
    pageSize: 10,
    configKey: '',
    bucketName: '',
    status: '',
    status: ''
  },
  rules: {
    configKey: [{ required: true, message: "configKey不能为空", trigger: "blur" },],
    configKey: [{ required: true, message: 'configKey不能为空', trigger: 'blur' }],
    accessKey: [
      { required: true, message: "accessKey不能为空", trigger: "blur" },
      { required: true, message: 'accessKey不能为空', trigger: 'blur' },
      {
        min: 2,
        max: 200,
        message: "accessKey长度必须介于 2 å’Œ 100 ä¹‹é—´",
        trigger: "blur",
      },
        message: 'accessKey长度必须介于 2 å’Œ 100 ä¹‹é—´',
        trigger: 'blur'
      }
    ],
    secretKey: [
      { required: true, message: "secretKey不能为空", trigger: "blur" },
      { required: true, message: 'secretKey不能为空', trigger: 'blur' },
      {
        min: 2,
        max: 100,
        message: "secretKey长度必须介于 2 å’Œ 100 ä¹‹é—´",
        trigger: "blur",
      },
        message: 'secretKey长度必须介于 2 å’Œ 100 ä¹‹é—´',
        trigger: 'blur'
      }
    ],
    bucketName: [
      { required: true, message: "bucketName不能为空", trigger: "blur" },
      { required: true, message: 'bucketName不能为空', trigger: 'blur' },
      {
        min: 2,
        max: 100,
        message: "bucketName长度必须介于 2 å’Œ 100 ä¹‹é—´",
        trigger: "blur",
      },
        message: 'bucketName长度必须介于 2 å’Œ 100 ä¹‹é—´',
        trigger: 'blur'
      }
    ],
    endpoint: [
      { required: true, message: "endpoint不能为空", trigger: "blur" },
      { required: true, message: 'endpoint不能为空', trigger: 'blur' },
      {
        min: 2,
        max: 100,
        message: "endpoint名称长度必须介于 2 å’Œ 100 ä¹‹é—´",
        trigger: "blur",
      },
        message: 'endpoint名称长度必须介于 2 å’Œ 100 ä¹‹é—´',
        trigger: 'blur'
      }
    ],
    accessPolicy: [{ required: true, message: "accessPolicy不能为空", trigger: "blur" }]
    accessPolicy: [{ required: true, message: 'accessPolicy不能为空', trigger: 'blur' }]
  }
});
@@ -253,39 +244,39 @@
  ossConfigList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
};
/** å–消按钮 */
const cancel = () => {
  dialog.visible = false;
  reset();
}
};
/** è¡¨å•重置 */
const reset = () => {
  form.value = { ...initFormData };
  ossConfigFormRef.value?.resetFields();
}
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value?.resetFields();
  handleQuery();
}
};
/** é€‰æ‹©æ¡æ•°  */
const handleSelectionChange = (selection: OssConfigVO[]) => {
  ids.value = selection.map(item => item.ossConfigId);
  ids.value = selection.map((item) => item.ossConfigId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
};
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
  reset();
  dialog.visible = true;
  dialog.title = "添加对象存储配置";
}
  dialog.title = '添加对象存储配置';
};
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = async (row?: OssConfigVO) => {
  reset();
@@ -293,49 +284,49 @@
  const res = await getOssConfig(ossConfigId);
  Object.assign(form.value, res.data);
  dialog.visible = true;
  dialog.title = "修改对象存储配置";
}
  dialog.title = '修改对象存储配置';
};
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  ossConfigFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      buttonLoading.value = true;
      if (form.value.ossConfigId) {
        await updateOssConfig(form.value).finally(() => buttonLoading.value = false);
        await updateOssConfig(form.value).finally(() => (buttonLoading.value = false));
      } else {
        await addOssConfig(form.value).finally(() => buttonLoading.value = false);
        await addOssConfig(form.value).finally(() => (buttonLoading.value = false));
      }
      proxy?.$modal.msgSuccess("新增成功");
      proxy?.$modal.msgSuccess('新增成功');
      dialog.visible = false;
      await getList();
    }
  });
}
};
/** çŠ¶æ€ä¿®æ”¹  */
const handleStatusChange = async (row: OssConfigVO) => {
  let text = row.status === "0" ? "启用" : "停用";
  let text = row.status === '0' ? '启用' : '停用';
  try {
    await proxy?.$modal.confirm('确认要"' + text + '""' + row.configKey + '"配置吗?');
    await changeOssConfigStatus(row.ossConfigId, row.status, row.configKey);
    await getList()
    proxy?.$modal.msgSuccess(text + "成功");
  } catch { return } finally {
    row.status = row.status === "0" ? "1" : "0";
    await getList();
    proxy?.$modal.msgSuccess(text + '成功');
  } catch {
    return;
  } finally {
    row.status = row.status === '0' ? '1' : '0';
  }
}
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: OssConfigVO) => {
  const ossConfigIds = row?.ossConfigId || ids.value;
  await proxy?.$modal.confirm('是否确认删除OSS配置编号为"' + ossConfigIds + '"的数据项?');
  loading.value = true;
  await delOssConfig(ossConfigIds).finally(() => loading.value = false);
  await delOssConfig(ossConfigIds).finally(() => (loading.value = false));
  await getList();
  proxy?.$modal.msgSuccess("删除成功");
}
  proxy?.$modal.msgSuccess('删除成功');
};
onMounted(() => {
  getList();
})
});
</script>
src/views/system/oss/index.vue
@@ -1,9 +1,9 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="mb-[10px]" v-show="showSearch">
      <div v-show="showSearch" class="mb-[10px]">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
            <el-form-item label="文件名" prop="fileName">
              <el-input v-model="queryParams.fileName" placeholder="请输入文件名" clearable style="width: 200px" @keyup.enter="handleQuery" />
            </el-form-item>
@@ -40,44 +40,42 @@
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Upload" @click="handleFile" v-hasPermi="['system:oss:upload']">上传文件</el-button>
            <el-button v-hasPermi="['system:oss:upload']" type="primary" plain icon="Upload" @click="handleFile">上传文件</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Upload" @click="handleImage" v-hasPermi="['system:oss:upload']">上传图片</el-button>
            <el-button v-hasPermi="['system:oss:upload']" type="primary" plain icon="Upload" @click="handleImage">上传图片</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:oss:remove']">
            <el-button v-hasPermi="['system:oss:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
              åˆ é™¤
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button
              v-hasPermi="['system:oss:edit']"
              :type="previewListResource ? 'danger' : 'warning'"
              plain
              @click="handlePreviewListResource(!previewListResource)"
              v-hasPermi="['system:oss:edit']"
              >预览开关 :
              {{
                previewListResource ? "禁用" : "启用" }}</el-button
              >预览开关 : {{ previewListResource ? '禁用' : '启用' }}</el-button
            >
          </el-col>
          <el-col :span="1.5">
            <el-button type="info" plain icon="Operation" @click="handleOssConfig" v-hasPermi="['system:oss:list']">配置管理</el-button>
            <el-button v-hasPermi="['system:oss:list']" type="info" plain icon="Operation" @click="handleOssConfig">配置管理</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table
        v-if="showTable"
        v-loading="loading"
        :data="ossList"
        @selection-change="handleSelectionChange"
        :header-cell-class-name="handleHeaderClass"
        @selection-change="handleSelectionChange"
        @header-click="handleHeaderCLick"
        v-if="showTable"
      >
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="对象存储主键" align="center" prop="ossId" v-if="false" />
        <el-table-column v-if="false" label="对象存储主键" align="center" prop="ossId" />
        <el-table-column label="文件名" align="center" prop="fileName" />
        <el-table-column label="原名" align="center" prop="originalName" />
        <el-table-column label="文件后缀" align="center" prop="fileSuffix" />
@@ -90,7 +88,7 @@
              :src="scope.row.url"
              :preview-src-list="[scope.row.url]"
            />
            <span v-text="scope.row.url" v-if="!checkFileSuffix(scope.row.fileSuffix) || !previewListResource" />
            <span v-if="!checkFileSuffix(scope.row.fileSuffix) || !previewListResource" v-text="scope.row.url" />
          </template>
        </el-table-column>
        <el-table-column label="创建时间" align="center" prop="createTime" width="180" sortable="custom">
@@ -103,23 +101,23 @@
        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="下载" placement="top">
              <el-button link type="primary" icon="Download" @click="handleDownload(scope.row)" v-hasPermi="['system:oss:download']"></el-button>
              <el-button v-hasPermi="['system:oss:download']" link type="primary" icon="Download" @click="handleDownload(scope.row)"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:oss:remove']"></el-button>
              <el-button v-hasPermi="['system:oss:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹OSS对象存储对话框 -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
      <el-form ref="ossFormRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="文件名">
          <fileUpload v-model="form.file" v-if="type === 0" />
          <imageUpload v-model="form.file" v-if="type === 1" />
          <fileUpload v-if="type === 0" v-model="form.file" />
          <imageUpload v-if="type === 1" v-model="form.file" />
        </el-form-item>
      </el-form>
      <template #footer>
@@ -133,9 +131,9 @@
</template>
<script setup name="Oss" lang="ts">
import { listOss, delOss } from "@/api/system/oss";
import ImagePreview from "@/components/ImagePreview/index.vue";
import { OssForm, OssQuery, OssVO } from "@/api/system/oss/types";
import { listOss, delOss } from '@/api/system/oss';
import ImagePreview from '@/components/ImagePreview/index.vue';
import { OssForm, OssQuery, OssVO } from '@/api/system/oss/types';
const router = useRouter();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -165,8 +163,8 @@
const queryFormRef = ref<ElFormInstance>();
const initFormData = {
  file: undefined,
}
  file: undefined
};
const data = reactive<PageData<OssForm, OssQuery>>({
  form: { ...initFormData },
  // æŸ¥è¯¢å‚æ•°
@@ -182,9 +180,7 @@
    isAsc: defaultSort.value.order
  },
  rules: {
    file: [
      { required: true, message: "文件不能为空", trigger: "blur" }
    ]
    file: [{ required: true, message: '文件不能为空', trigger: 'blur' }]
  }
});
@@ -193,17 +189,17 @@
/** æŸ¥è¯¢OSS对象存储列表 */
const getList = async () => {
  loading.value = true;
  const res = await proxy?.getConfigKey("sys.oss.previewListResource");
  const res = await proxy?.getConfigKey('sys.oss.previewListResource');
  previewListResource.value = res?.data === undefined ? true : res.data === 'true';
  const response = await listOss(proxy?.addDateRange(queryParams.value, dateRangeCreateTime.value, "CreateTime"));
  const response = await listOss(proxy?.addDateRange(queryParams.value, dateRangeCreateTime.value, 'CreateTime'));
  ossList.value = response.rows;
  total.value = response.total;
  loading.value = false;
  showTable.value = true;
}
};
function checkFileSuffix(fileSuffix: string[]) {
  let arr = ["png", "jpg", "jpeg"];
  return arr.some(type => {
  let arr = ['png', 'jpg', 'jpeg'];
  return arr.some((type) => {
    return fileSuffix.indexOf(type) > -1;
  });
}
@@ -233,18 +229,18 @@
}
/** é€‰æ‹©æ¡æ•°  */
function handleSelectionChange(selection: OssVO[]) {
  ids.value = selection.map(item => item.ossId);
  ids.value = selection.map((item) => item.ossId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** è®¾ç½®åˆ—的排序为我们自定义的排序 */
const handleHeaderClass = ({ column }: any): any => {
  column.order = column.multiOrder
}
  column.order = column.multiOrder;
};
/** ç‚¹å‡»è¡¨å¤´è¿›è¡ŒæŽ’序 */
const handleHeaderCLick = (column: any) => {
  if (column.sortable !== 'custom') {
    return
    return;
  }
  switch (column.multiOrder) {
    case 'descending':
@@ -257,20 +253,20 @@
      column.multiOrder = 'descending';
      break;
  }
  handleOrderChange(column.property, column.multiOrder)
}
  handleOrderChange(column.property, column.multiOrder);
};
const handleOrderChange = (prop: string, order: string) => {
  let orderByArr = queryParams.value.orderByColumn ? queryParams.value.orderByColumn.split(",") : [];
  let isAscArr = queryParams.value.isAsc ? queryParams.value.isAsc.split(",") : [];
  let propIndex = orderByArr.indexOf(prop)
  let orderByArr = queryParams.value.orderByColumn ? queryParams.value.orderByColumn.split(',') : [];
  let isAscArr = queryParams.value.isAsc ? queryParams.value.isAsc.split(',') : [];
  let propIndex = orderByArr.indexOf(prop);
  if (propIndex !== -1) {
    if (order) {
      //排序里已存在 åªä¿®æ”¹æŽ’序
      isAscArr[propIndex] = order;
    } else {
      //如果order为null åˆ™åˆ é™¤æŽ’序字段和属性
      isAscArr.splice(propIndex, 1);//删除排序
      orderByArr.splice(propIndex, 1);//删除属性
      isAscArr.splice(propIndex, 1); //删除排序
      orderByArr.splice(propIndex, 1); //删除属性
    }
  } else {
    //排序里不存在则新增排序
@@ -278,58 +274,60 @@
    isAscArr.push(order);
  }
  //合并排序
  queryParams.value.orderByColumn = orderByArr.join(",");
  queryParams.value.isAsc = isAscArr.join(",");
  queryParams.value.orderByColumn = orderByArr.join(',');
  queryParams.value.isAsc = isAscArr.join(',');
  getList();
}
};
/** ä»»åŠ¡æ—¥å¿—åˆ—è¡¨æŸ¥è¯¢ */
const handleOssConfig = () => {
  router.push('/system/oss-config/index')
}
  router.push('/system/oss-config/index');
};
/** æ–‡ä»¶æŒ‰é’®æ“ä½œ */
const handleFile = () => {
  reset();
  type.value = 0;
  dialog.visible = true;
  dialog.title = "上传文件";
}
  dialog.title = '上传文件';
};
/** å›¾ç‰‡æŒ‰é’®æ“ä½œ */
const handleImage = () => {
  reset();
  type.value = 1;
  dialog.visible = true;
  dialog.title = "上传图片";
}
  dialog.title = '上传图片';
};
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  dialog.visible = false;
  getList();
}
};
/** ä¸‹è½½æŒ‰é’®æ“ä½œ */
const handleDownload = (row: OssVO) => {
  proxy?.$download.oss(row.ossId)
}
  proxy?.$download.oss(row.ossId);
};
/** ç”¨æˆ·çŠ¶æ€ä¿®æ”¹  */
const handlePreviewListResource = async (preview: boolean) => {
  let text = preview ? "启用" : "停用";
  let text = preview ? '启用' : '停用';
  try {
    await proxy?.$modal.confirm('确认要"' + text + '""预览列表图片"配置吗?');
    await proxy?.updateConfigByKey("sys.oss.previewListResource", preview);
    await getList()
    proxy?.$modal.msgSuccess(text + "成功");
  } catch { return }
}
    await proxy?.updateConfigByKey('sys.oss.previewListResource', preview);
    await getList();
    proxy?.$modal.msgSuccess(text + '成功');
  } catch {
    return;
  }
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: OssVO) => {
  const ossIds = row?.ossId || ids.value;
  await proxy?.$modal.confirm('是否确认删除OSS对象存储编号为"' + ossIds + '"的数据项?');
  loading.value = true;
  await delOss(ossIds).finally(() => loading.value = false);
  await delOss(ossIds).finally(() => (loading.value = false));
  await getList();
  proxy?.$modal.msgSuccess("删除成功");
}
  proxy?.$modal.msgSuccess('删除成功');
};
onMounted(() => {
  getList();
})
});
</script>
src/views/system/post/index.vue
@@ -1,9 +1,9 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="mb-[10px]" v-show="showSearch">
      <div v-show="showSearch" class="mb-[10px]">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="70">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="70">
            <el-form-item label="岗位编码" prop="postCode">
              <el-input v-model="queryParams.postCode" placeholder="请输入岗位编码" clearable style="width: 200px" @keyup.enter="handleQuery" />
            </el-form-item>
@@ -27,26 +27,26 @@
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:post:add']">新增</el-button>
            <el-button v-hasPermi="['system:post:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:post:edit']">修改</el-button>
            <el-button v-hasPermi="['system:post:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:post:remove']">
            <el-button v-hasPermi="['system:post:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
              åˆ é™¤
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:post:export']">导出</el-button>
            <el-button v-hasPermi="['system:post:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="postList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="岗位编号" align="center" prop="postId" v-if="false" />
        <el-table-column v-if="false" label="岗位编号" align="center" prop="postId" />
        <el-table-column label="岗位编码" align="center" prop="postCode" />
        <el-table-column label="岗位名称" align="center" prop="postName" />
        <el-table-column label="岗位排序" align="center" prop="postSort" />
@@ -63,20 +63,20 @@
        <el-table-column label="操作" width="180" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:post:edit']"></el-button>
              <el-button v-hasPermi="['system:post:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:post:remove']"></el-button>
              <el-button v-hasPermi="['system:post:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹å²—ä½å¯¹è¯æ¡† -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
      <el-form ref="postFormRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="岗位名称" prop="postName">
          <el-input v-model="form.postName" placeholder="请输入岗位名称" />
@@ -107,11 +107,11 @@
</template>
<script setup name="Post" lang="ts">
import { listPost, addPost, delPost, getPost, updatePost } from "@/api/system/post";
import { PostForm, PostQuery, PostVO } from "@/api/system/post/types";
import { listPost, addPost, delPost, getPost, updatePost } from '@/api/system/post';
import { PostForm, PostQuery, PostVO } from '@/api/system/post/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict("sys_normal_disable"));
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
const postList = ref<PostVO[]>([]);
const loading = ref(true);
@@ -134,9 +134,9 @@
  postCode: '',
  postName: '',
  postSort: 0,
  status: "0",
  status: '0',
  remark: ''
}
};
const data = reactive<PageData<PostForm, PostQuery>>({
  form: { ...initFormData },
@@ -148,9 +148,9 @@
    status: ''
  },
  rules: {
    postName: [{ required: true, message: "岗位名称不能为空", trigger: "blur" }],
    postCode: [{ required: true, message: "岗位编码不能为空", trigger: "blur" }],
    postSort: [{ required: true, message: "岗位顺序不能为空", trigger: "blur" }],
    postName: [{ required: true, message: '岗位名称不能为空', trigger: 'blur' }],
    postCode: [{ required: true, message: '岗位编码不能为空', trigger: 'blur' }],
    postSort: [{ required: true, message: '岗位顺序不能为空', trigger: 'blur' }]
  }
});
@@ -163,39 +163,39 @@
  postList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
};
/** å–消按钮 */
const cancel = () => {
  reset();
  dialog.visible = false;
}
};
/** è¡¨å•重置 */
const reset = () => {
  form.value = { ...initFormData };
  postFormRef.value?.resetFields();
}
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value?.resetFields();
  handleQuery();
}
};
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: PostVO[]) => {
  ids.value = selection.map(item => item.postId);
  ids.value = selection.map((item) => item.postId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
};
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
  reset();
  dialog.visible = true;
  dialog.title = "添加岗位";
}
  dialog.title = '添加岗位';
};
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = async (row?: PostVO) => {
  reset();
@@ -203,33 +203,37 @@
  const res = await getPost(postId);
  Object.assign(form.value, res.data);
  dialog.visible = true;
  dialog.title = "修改岗位";
}
  dialog.title = '修改岗位';
};
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  postFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      form.value.postId ? await updatePost(form.value) : await addPost(form.value);
      proxy?.$modal.msgSuccess("操作成功");
      proxy?.$modal.msgSuccess('操作成功');
      dialog.visible = false;
      await getList();
    }
  });
}
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: PostVO) => {
  const postIds = row?.postId || ids.value;
  await proxy?.$modal.confirm('是否确认删除岗位编号为"' + postIds + '"的数据项?');
  await delPost(postIds);
  await getList();
  proxy?.$modal.msgSuccess("删除成功");
}
  proxy?.$modal.msgSuccess('删除成功');
};
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
  proxy?.download("system/post/export", {
    ...queryParams.value
  }, `post_${new Date().getTime()}.xlsx`);
}
  proxy?.download(
    'system/post/export',
    {
      ...queryParams.value
    },
    `post_${new Date().getTime()}.xlsx`
  );
};
onMounted(() => {
  getList();
src/views/system/role/authUser.vue
@@ -1,8 +1,8 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="search" v-show="showSearch">
        <el-form :model="queryParams" ref="queryFormRef" :inline="true">
      <div v-show="showSearch" class="search">
        <el-form ref="queryFormRef" :model="queryParams" :inline="true">
          <el-form-item label="用户名称" prop="userName">
            <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
          </el-form-item>
@@ -20,17 +20,17 @@
      <template #header>
        <el-row :gutter="10">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="openSelectUser" v-hasPermi="['system:role:add']">添加用户</el-button>
            <el-button v-hasPermi="['system:role:add']" type="primary" plain icon="Plus" @click="openSelectUser">添加用户</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="CircleClose" :disabled="multiple" @click="cancelAuthUserAll" v-hasPermi="['system:role:remove']">
            <el-button v-hasPermi="['system:role:remove']" type="danger" plain icon="CircleClose" :disabled="multiple" @click="cancelAuthUserAll">
              æ‰¹é‡å–消授权
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Close" @click="handleClose">关闭</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :search="true"></right-toolbar>
          <right-toolbar v-model:showSearch="showSearch" :search="true" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
@@ -52,28 +52,27 @@
        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="取消授权" placement="top">
              <el-button link type="primary" icon="CircleClose" @click="cancelAuthUser(scope.row)" v-hasPermi="['system:role:remove']"> </el-button>
              <el-button v-hasPermi="['system:role:remove']" link type="primary" icon="CircleClose" @click="cancelAuthUser(scope.row)"> </el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
      <select-user ref="selectRef" :roleId="queryParams.roleId" @ok="handleQuery" />
      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
      <select-user ref="selectRef" :role-id="queryParams.roleId" @ok="handleQuery" />
    </el-card>
  </div>
</template>
<script setup name="AuthUser" lang="ts">
import { allocatedUserList, authUserCancel, authUserCancelAll } from "@/api/system/role";
import { UserQuery } from "@/api/system/user/types";
import { UserVO } from "@/api/system/user/types";
import SelectUser from "./selectUser.vue";
import { allocatedUserList, authUserCancel, authUserCancelAll } from '@/api/system/role';
import { UserQuery } from '@/api/system/user/types';
import { UserVO } from '@/api/system/user/types';
import SelectUser from './selectUser.vue';
const route = useRoute();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict("sys_normal_disable"));
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
const userList = ref<UserVO[]>([]);
const loading = ref(true);
@@ -90,7 +89,7 @@
  pageSize: 10,
  roleId: route.params.roleId as string,
  userName: undefined,
  phonenumber: undefined,
  phonenumber: undefined
});
/** æŸ¥è¯¢æŽˆæƒç”¨æˆ·åˆ—表 */
@@ -100,47 +99,47 @@
  userList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
};
// è¿”回按钮
const handleClose = () => {
  const obj = { path: "/system/role" };
  const obj = { path: '/system/role' };
  proxy?.$tab.closeOpenPage(obj);
}
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.pageNum = 1;
  getList();
}
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value?.resetFields();
  handleQuery();
}
};
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
const handleSelectionChange = (selection: UserVO[]) => {
  userIds.value = selection.map(item => item.userId);
  userIds.value = selection.map((item) => item.userId);
  multiple.value = !selection.length;
}
};
/** æ‰“开授权用户表弹窗 */
const openSelectUser = () => {
  selectRef.value?.show();
}
};
/** å–消授权按钮操作 */
const cancelAuthUser = async (row: UserVO) => {
  await proxy?.$modal.confirm('确认要取消该用户"' + row.userName + '"角色吗?');
  await authUserCancel({ userId: row.userId, roleId: queryParams.roleId });
  await getList();
  proxy?.$modal.msgSuccess("取消授权成功");
}
  proxy?.$modal.msgSuccess('取消授权成功');
};
/** æ‰¹é‡å–消授权按钮操作 */
const cancelAuthUserAll = async () => {
  const roleId = queryParams.roleId;
  const uIds = userIds.value.join(",");
  await proxy?.$modal.confirm("是否取消选中用户授权数据项?");
  const uIds = userIds.value.join(',');
  await proxy?.$modal.confirm('是否取消选中用户授权数据项?');
  await authUserCancelAll({ roleId: roleId, userIds: uIds });
  await getList();
  proxy?.$modal.msgSuccess("取消授权成功");
}
  proxy?.$modal.msgSuccess('取消授权成功');
};
onMounted(() => {
  getList();
src/views/system/role/index.vue
@@ -1,7 +1,7 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="mb-[10px]" v-show="showSearch">
      <div v-show="showSearch" class="mb-[10px]">
        <el-card shadow="hover">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
            <el-form-item label="角色名称" prop="roleName">
@@ -28,8 +28,8 @@
            </el-form-item>
            <el-form-item>
              <el-button type="primary" @click="handleQuery" icon="Search">搜索</el-button>
              <el-button @click="resetQuery" icon="Refresh">重置</el-button>
              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
            </el-form-item>
          </el-form>
        </el-card>
@@ -40,24 +40,24 @@
      <template #header>
        <el-row :gutter="10">
          <el-col :span="1.5">
            <el-button type="primary" plain @click="handleAdd()" icon="Plus" v-hasPermi="['system:role:add']">新增</el-button>
            <el-button v-hasPermi="['system:role:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="success" plain @click="handleUpdate()" :disabled="single" icon="Edit" v-hasPermi="['system:role:edit']">修改</el-button>
            <el-button v-hasPermi="['system:role:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">修改</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain :disabled="ids.length === 0" @click="handleDelete()" v-hasPermi="['system:role:delete']">删除</el-button>
            <el-button v-hasPermi="['system:role:delete']" type="danger" plain :disabled="ids.length === 0" @click="handleDelete()">删除</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:role:export']">导出</el-button>
            <el-button v-hasPermi="['system:role:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table ref="roleTableRef" v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="角色编号" prop="roleId" width="120" v-if="false" />
        <el-table-column v-if="false" label="角色编号" prop="roleId" width="120" />
        <el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />
        <el-table-column label="权限字符" prop="roleKey" :show-overflow-tooltip="true" width="200" />
        <el-table-column label="显示顺序" prop="roleSort" width="100" />
@@ -74,17 +74,17 @@
        <el-table-column fixed="right" label="操作" width="180">
          <template #default="scope">
            <el-tooltip content="修改" placement="top" v-if="scope.row.roleId !== 1">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:role:edit']"></el-button>
            <el-tooltip v-if="scope.row.roleId !== 1" content="修改" placement="top">
              <el-button v-hasPermi="['system:role:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top" v-if="scope.row.roleId !== 1">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:role:remove']"></el-button>
            <el-tooltip v-if="scope.row.roleId !== 1" content="删除" placement="top">
              <el-button v-hasPermi="['system:role:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
            </el-tooltip>
            <el-tooltip content="数据权限" placement="top" v-if="scope.row.roleId !== 1">
              <el-button link type="primary" icon="CircleCheck" @click="handleDataScope(scope.row)" v-hasPermi="['system:role:edit']"></el-button>
            <el-tooltip v-if="scope.row.roleId !== 1" content="数据权限" placement="top">
              <el-button v-hasPermi="['system:role:edit']" link type="primary" icon="CircleCheck" @click="handleDataScope(scope.row)"></el-button>
            </el-tooltip>
            <el-tooltip content="分配用户" placement="top" v-if="scope.row.roleId !== 1">
              <el-button link type="primary" icon="User" @click="handleAuthUser(scope.row)" v-hasPermi="['system:role:edit']"></el-button>
            <el-tooltip v-if="scope.row.roleId !== 1" content="分配用户" placement="top">
              <el-button v-hasPermi="['system:role:edit']" link type="primary" icon="User" @click="handleAuthUser(scope.row)"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
@@ -99,7 +99,7 @@
      />
    </el-card>
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
      <el-form ref="roleFormRef" :model="form" :rules="rules" label-width="100px">
        <el-form-item label="角色名称" prop="roleName">
          <el-input v-model="form.roleName" placeholder="请输入角色名称" />
@@ -120,9 +120,7 @@
        </el-form-item>
        <el-form-item label="状态">
          <el-radio-group v-model="form.status">
            <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">{{
              dict.label
            }}</el-radio>
            <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="菜单权限">
@@ -130,10 +128,10 @@
          <el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选</el-checkbox>
          <el-checkbox v-model="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')">父子联动</el-checkbox>
          <el-tree
            ref="menuRef"
            class="tree-border"
            :data="menuOptions"
            show-checkbox
            ref="menuRef"
            node-key="id"
            :check-strictly="!form.menuCheckStrictly"
            empty-text="加载中,请稍候"
@@ -153,8 +151,8 @@
    </el-dialog>
    <!-- åˆ†é…è§’色数据权限对话框 -->
    <el-dialog :title="dialog.title" v-model="openDataScope" width="500px" append-to-body>
      <el-form :model="form" label-width="80px" ref="dataScopeRef">
    <el-dialog v-model="openDataScope" :title="dialog.title" width="500px" append-to-body>
      <el-form ref="dataScopeRef" :model="form" label-width="80px">
        <el-form-item label="角色名称">
          <el-input v-model="form.roleName" :disabled="true" />
        </el-form-item>
@@ -166,16 +164,16 @@
            <el-option v-for="item in dataScopeOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="数据权限" v-show="form.dataScope === '2'">
        <el-form-item v-show="form.dataScope === '2'" label="数据权限">
          <el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand($event, 'dept')">展开/折叠</el-checkbox>
          <el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">全选/全不选</el-checkbox>
          <el-checkbox v-model="form.deptCheckStrictly" @change="handleCheckedTreeConnect($event, 'dept')">父子联动</el-checkbox>
          <el-tree
            ref="deptRef"
            class="tree-border"
            :data="deptOptions"
            show-checkbox
            default-expand-all
            ref="deptRef"
            node-key="id"
            :check-strictly="!form.deptCheckStrictly"
            empty-text="加载中,请稍候"
@@ -194,7 +192,7 @@
</template>
<script setup name="Role" lang="ts">
import { addRole, changeRoleStatus, dataScope, delRole, getRole, listRole, updateRole, deptTreeSelect } from "@/api/system/role";
import { addRole, changeRoleStatus, dataScope, delRole, getRole, listRole, updateRole, deptTreeSelect } from '@/api/system/role';
import { roleMenuTreeselect, treeselect as menuTreeselect } from '@/api/system/menu/index';
import { RoleVO, RoleForm, RoleQuery, DeptTreeOption } from '@/api/system/role/types';
import { MenuTreeOption, RoleMenuTree } from '@/api/system/menu/types';
@@ -204,29 +202,29 @@
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
const roleList = ref<RoleVO[]>();
const loading = ref(true)
const showSearch = ref(true)
const ids = ref<Array<string | number>>([])
const single = ref(true)
const multiple = ref(true)
const total = ref(0)
const dateRange = ref<[DateModelType, DateModelType]>(['', ''])
const menuOptions = ref<MenuTreeOption[]>([])
const menuExpand = ref(false)
const menuNodeAll = ref(false)
const deptExpand = ref(true)
const deptNodeAll = ref(false)
const deptOptions = ref<DeptTreeOption[]>([])
const openDataScope = ref(false)
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const menuOptions = ref<MenuTreeOption[]>([]);
const menuExpand = ref(false);
const menuNodeAll = ref(false);
const deptExpand = ref(true);
const deptNodeAll = ref(false);
const deptOptions = ref<DeptTreeOption[]>([]);
const openDataScope = ref(false);
/** æ•°æ®èŒƒå›´é€‰é¡¹*/
const dataScopeOptions = ref([
  { value: "1", label: "全部数据权限" },
  { value: "2", label: "自定数据权限" },
  { value: "3", label: "本部门数据权限" },
  { value: "4", label: "本部门及以下数据权限" },
  { value: "5", label: "仅本人数据权限" }
])
  { value: '1', label: '全部数据权限' },
  { value: '2', label: '自定数据权限' },
  { value: '3', label: '本部门数据权限' },
  { value: '4', label: '本部门及以下数据权限' },
  { value: '5', label: '仅本人数据权限' }
]);
const queryFormRef = ref<ElFormInstance>();
const roleFormRef = ref<ElFormInstance>();
@@ -245,8 +243,8 @@
  remark: '',
  dataScope: '1',
  menuIds: [],
  deptIds: [],
}
  deptIds: []
};
const data = reactive<PageData<RoleForm, RoleQuery>>({
  form: { ...initForm },
@@ -255,15 +253,15 @@
    pageSize: 10,
    roleName: '',
    roleKey: '',
    status: '',
    status: ''
  },
  rules: {
    roleName: [{ required: true, message: "角色名称不能为空", trigger: "blur" }],
    roleKey: [{ required: true, message: "权限字符不能为空", trigger: "blur" }],
    roleSort: [{ required: true, message: "角色顺序不能为空", trigger: "blur" }]
    roleName: [{ required: true, message: '角色名称不能为空', trigger: 'blur' }],
    roleKey: [{ required: true, message: '权限字符不能为空', trigger: 'blur' }],
    roleSort: [{ required: true, message: '角色顺序不能为空', trigger: 'blur' }]
  }
})
const { form, queryParams, rules } = toRefs(data)
});
const { form, queryParams, rules } = toRefs(data);
const dialog = reactive<DialogOption>({
  visible: false,
@@ -274,13 +272,13 @@
 * æŸ¥è¯¢è§’色列表
 */
const getList = () => {
  loading.value = true
  listRole(proxy?.addDateRange(queryParams.value, dateRange.value)).then(res => {
    roleList.value = res.rows
    total.value = res.total
    loading.value = false
  })
}
  loading.value = true;
  listRole(proxy?.addDateRange(queryParams.value, dateRange.value)).then((res) => {
    roleList.value = res.rows;
    total.value = res.total;
    loading.value = false;
  });
};
/**
 * æœç´¢æŒ‰é’®æ“ä½œ
@@ -288,14 +286,14 @@
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
};
/** é‡ç½® */
const resetQuery = () => {
  dateRange.value = ['', '']
  dateRange.value = ['', ''];
  queryFormRef.value?.resetFields();
  handleQuery();
}
};
/**删除按钮操作 */
const handleDelete = async (row?: RoleVO) => {
  const roleids = row?.roleId || ids.value;
@@ -303,43 +301,47 @@
  await delRole(roleids);
  getList();
  proxy?.$modal.msgSuccess('删除成功');
}
};
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
  proxy?.download("system/role/export", {
    ...queryParams.value,
  }, `role_${new Date().getTime()}.xlsx`)
}
  proxy?.download(
    'system/role/export',
    {
      ...queryParams.value
    },
    `role_${new Date().getTime()}.xlsx`
  );
};
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: RoleVO[]) => {
  ids.value = selection.map((item: RoleVO) => item.roleId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
};
/** è§’色状态修改 */
const handleStatusChange = async (row: RoleVO) => {
  let text = row.status === "0" ? "启用" : "停用";
  let text = row.status === '0' ? '启用' : '停用';
  try {
    await proxy?.$modal.confirm('确认要"' + text + '""' + row.roleName + '"角色吗?');
    await changeRoleStatus(row.roleId, row.status);
    proxy?.$modal.msgSuccess(text + "成功");
    proxy?.$modal.msgSuccess(text + '成功');
  } catch {
    row.status = row.status === "0" ? "1" : "0";
    row.status = row.status === '0' ? '1' : '0';
  }
}
};
/** åˆ†é…ç”¨æˆ· */
const handleAuthUser = (row: RoleVO) => {
  router.push("/system/role-auth/user/" + row.roleId);
}
  router.push('/system/role-auth/user/' + row.roleId);
};
/** æŸ¥è¯¢èœå•树结构 */
const getMenuTreeselect = async () => {
  const res = await menuTreeselect();
  menuOptions.value = res.data;
}
};
/** æ‰€æœ‰éƒ¨é—¨èŠ‚ç‚¹æ•°æ® */
const getDeptAllCheckedKeys = (): any => {
  // ç›®å‰è¢«é€‰ä¸­çš„部门节点
@@ -349,67 +351,65 @@
  if (halfCheckedKeys) {
    checkedKeys?.unshift.apply(checkedKeys, halfCheckedKeys);
  }
  return checkedKeys
}
  return checkedKeys;
};
/** é‡ç½®æ–°å¢žçš„表单以及其他数据  */
const reset = () => {
  menuRef.value?.setCheckedKeys([]);
  menuExpand.value = false
  menuNodeAll.value = false
  deptExpand.value = true
  deptNodeAll.value = false
  menuExpand.value = false;
  menuNodeAll.value = false;
  deptExpand.value = true;
  deptNodeAll.value = false;
  form.value = { ...initForm };
  roleFormRef.value?.resetFields();
}
};
/** æ·»åŠ è§’è‰² */
const handleAdd = () => {
  reset();
  getMenuTreeselect();
  dialog.visible = true;
  dialog.title = "添加角色";
}
  dialog.title = '添加角色';
};
/** ä¿®æ”¹è§’色 */
const handleUpdate = async (row?: RoleVO) => {
  reset();
  const roleId = row?.roleId || ids.value[0]
  const roleId = row?.roleId || ids.value[0];
  const { data } = await getRole(roleId);
  Object.assign(form.value, data);
  form.value.roleSort = Number(form.value.roleSort);
  const res = await getRoleMenuTreeselect(roleId);
  dialog.title = "修改角色";
  dialog.title = '修改角色';
  dialog.visible = true;
  res.checkedKeys.forEach((v) => {
    nextTick(() => {
      menuRef.value?.setChecked(v, true, false);
    })
  })
}
    });
  });
};
/** æ ¹æ®è§’色ID查询菜单树结构 */
const getRoleMenuTreeselect = (roleId: string | number) => {
  return roleMenuTreeselect(roleId).then((res): RoleMenuTree => {
    menuOptions.value = res.data.menus;
    return res.data;
  })
}
  });
};
/** æ ¹æ®è§’色ID查询部门树结构 */
const getRoleDeptTreeSelect = async (roleId: string | number) => {
  const res = await deptTreeSelect(roleId);
  deptOptions.value = res.data.depts;
  return res.data;
}
};
/** æ ‘权限(展开/折叠)*/
const handleCheckedTreeExpand = (value: boolean, type: string) => {
  if (type == "menu") {
  if (type == 'menu') {
    let treeList = menuOptions.value;
    for (let i = 0; i < treeList.length; i++) {
      if (menuRef.value) {
        menuRef.value.store.nodesMap[treeList[i].id].expanded = value;
      }
    }
  } else if (type == "dept") {
  } else if (type == 'dept') {
    let treeList = deptOptions.value;
    for (let i = 0; i < treeList.length; i++) {
      if (deptRef.value) {
@@ -417,23 +417,23 @@
      }
    }
  }
}
};
/** æ ‘权限(全选/全不选) */
const handleCheckedTreeNodeAll = (value: any, type: string) => {
  if (type == "menu") {
    menuRef.value?.setCheckedNodes(value ? menuOptions.value as any : []);
  } else if (type == "dept") {
    deptRef.value?.setCheckedNodes(value ? deptOptions.value as any : []);
  if (type == 'menu') {
    menuRef.value?.setCheckedNodes(value ? (menuOptions.value as any) : []);
  } else if (type == 'dept') {
    deptRef.value?.setCheckedNodes(value ? (deptOptions.value as any) : []);
  }
}
};
/** æ ‘权限(父子联动) */
const handleCheckedTreeConnect = (value: any, type: string) => {
  if (type == "menu") {
  if (type == 'menu') {
    form.value.menuCheckStrictly = value;
  } else if (type == "dept") {
  } else if (type == 'dept') {
    form.value.deptCheckStrictly = value;
  }
}
};
/** æ‰€æœ‰èœå•节点数据 */
const getMenuAllCheckedKeys = (): any => {
  // ç›®å‰è¢«é€‰ä¸­çš„菜单节点
@@ -444,57 +444,57 @@
    checkedKeys?.unshift.apply(checkedKeys, halfCheckedKeys);
  }
  return checkedKeys;
}
};
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  roleFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      form.value.menuIds = getMenuAllCheckedKeys()
      form.value.menuIds = getMenuAllCheckedKeys();
      form.value.roleId ? await updateRole(form.value) : await addRole(form.value);
      proxy?.$modal.msgSuccess("操作成功")
      dialog.visible = false
      getList()
      proxy?.$modal.msgSuccess('操作成功');
      dialog.visible = false;
      getList();
    }
  })
}
  });
};
/** å–消按钮 */
const cancel = () => {
  reset()
  reset();
  dialog.visible = false;
}
};
/** é€‰æ‹©è§’色权限范围触发 */
const dataScopeSelectChange = (value: string) => {
  if (value !== "2") {
    deptRef.value?.setCheckedKeys([])
  if (value !== '2') {
    deptRef.value?.setCheckedKeys([]);
  }
}
};
/** åˆ†é…æ•°æ®æƒé™æ“ä½œ */
const handleDataScope = async (row: RoleVO) => {
  const response = await getRole(row.roleId);
  Object.assign(form.value, response.data);
  const res = await getRoleDeptTreeSelect(row.roleId);
  openDataScope.value = true;
  dialog.title = "分配数据权限";
  dialog.title = '分配数据权限';
  await nextTick(() => {
    deptRef.value?.setCheckedKeys(res.checkedKeys);
  })
}
  });
};
/** æäº¤æŒ‰é’®ï¼ˆæ•°æ®æƒé™ï¼‰ */
const submitDataScope = async () => {
  if (form.value.roleId) {
    form.value.deptIds = getDeptAllCheckedKeys();
    await dataScope(form.value);
    proxy?.$modal.msgSuccess("修改成功");
    proxy?.$modal.msgSuccess('修改成功');
    openDataScope.value = false;
    getList();
  }
}
};
/** å–消按钮(数据权限)*/
const cancelDataScope = () => {
  dataScopeRef.value?.resetFields();
  form.value = { ...initForm };
  openDataScope.value = false;
}
};
onMounted(() => {
  getList();
src/views/system/role/selectUser.vue
@@ -1,7 +1,7 @@
<template>
  <el-row>
    <el-dialog title="选择用户" v-model="visible" width="800px" top="5vh" append-to-body>
      <el-form :model="queryParams" ref="queryFormRef" :inline="true">
    <el-dialog v-model="visible" title="选择用户" width="800px" top="5vh" append-to-body>
      <el-form ref="queryFormRef" :model="queryParams" :inline="true">
        <el-form-item label="用户名称" prop="userName">
          <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" />
        </el-form-item>
@@ -14,7 +14,7 @@
        </el-form-item>
      </el-form>
      <el-row>
        <el-table @row-click="clickRow" ref="tableRef" :data="userList" @selection-change="handleSelectionChange" height="260px">
        <el-table ref="tableRef" :data="userList" height="260px" @row-click="clickRow" @selection-change="handleSelectionChange">
          <el-table-column type="selection" width="55"></el-table-column>
          <el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
          <el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
@@ -31,7 +31,7 @@
            </template>
          </el-table-column>
        </el-table>
        <pagination v-if="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
        <pagination v-if="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
      </el-row>
      <template #footer>
        <div class="dialog-footer">
@@ -44,16 +44,15 @@
</template>
<script setup name="SelectUser" lang="ts">
import { authUserSelectAll, unallocatedUserList } from "@/api/system/role";
import { authUserSelectAll, unallocatedUserList } from '@/api/system/role';
import { UserVO } from '@/api/system/user/types';
import { UserQuery } from '@/api/system/user/types';
const props = defineProps({
  roleId: {
    type: [Number, String]
  }
})
});
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable'));
@@ -69,7 +68,7 @@
  roleId: undefined,
  userName: undefined,
  phonenumber: undefined
})
});
const tableRef = ref<ElTableInstance>();
const queryFormRef = ref<ElFormInstance>();
@@ -78,7 +77,7 @@
  queryParams.roleId = props.roleId;
  getList();
  visible.value = true;
}
};
/**
 * é€‰æ‹©è¡Œ
@@ -86,35 +85,35 @@
const clickRow = (row: any) => {
  // ele的bug
  tableRef.value?.toggleRowSelection(row, false);
}
};
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: UserVO[]) => {
  userIds.value = selection.map((item: UserVO) => item.userId);
}
};
/** æŸ¥è¯¢æ•°æ® */
const getList = async () => {
  const res = await unallocatedUserList(queryParams);
  userList.value = res.rows;
  total.value = res.total;
}
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.pageNum = 1;
  getList();
}
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value?.resetFields();
  getList();
}
};
const emit = defineEmits(["ok"]);
const emit = defineEmits(['ok']);
/**选择授权用户操作 */
const handleSelectUser = async () => {
  const roleId = queryParams.roleId;
  const ids = userIds.value.join(',');
  if (ids == "") {
  if (ids == '') {
    proxy?.$modal.msgError('请选择要分配的用户');
    return;
  }
@@ -122,10 +121,10 @@
  proxy?.$modal.msgSuccess('分配成功');
  emit('ok');
  visible.value = false;
}
};
// æš´éœ²
defineExpose({
  show,
  show
});
</script>
src/views/system/tenant/index.vue
@@ -1,9 +1,9 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="mb-[10px]" v-show="showSearch">
      <div v-show="showSearch" class="mb-[10px]">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
            <el-form-item label="租户编号" prop="tenantId">
              <el-input v-model="queryParams.tenantId" placeholder="请输入租户编号" clearable style="width: 240px" @keyup.enter="handleQuery" />
            </el-form-item>
@@ -29,28 +29,28 @@
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:tenant:add']">新增</el-button>
            <el-button v-hasPermi="['system:tenant:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:tenant:edit']"
            <el-button v-hasPermi="['system:tenant:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()"
              >修改</el-button
            >
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:tenant:remove']">
            <el-button v-hasPermi="['system:tenant:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
              åˆ é™¤
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:tenant:export']">导出</el-button>
            <el-button v-hasPermi="['system:tenant:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="tenantList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="id" align="center" prop="id" v-if="false" />
        <el-table-column v-if="false" label="id" align="center" prop="id" />
        <el-table-column label="租户编号" align="center" prop="tenantId" />
        <el-table-column label="联系人" align="center" prop="contactUserName" />
        <el-table-column label="联系电话" align="center" prop="contactPhone" />
@@ -69,23 +69,23 @@
        <el-table-column width="150" label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:tenant:edit']"></el-button>
              <el-button v-hasPermi="['system:tenant:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
            </el-tooltip>
            <el-tooltip content="同步套餐" placement="top">
              <el-button link type="primary" icon="Refresh" @click="handleSyncTenantPackage(scope.row)" v-hasPermi="['system:tenant:edit']">
              <el-button v-hasPermi="['system:tenant:edit']" link type="primary" icon="Refresh" @click="handleSyncTenantPackage(scope.row)">
              </el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:tenant:remove']"></el-button>
              <el-button v-hasPermi="['system:tenant:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹ç§Ÿæˆ·å¯¹è¯æ¡† -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
      <el-form ref="tenantFormRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="企业名称" prop="companyName">
          <el-input v-model="form.companyName" placeholder="请输入企业名称" />
@@ -100,7 +100,7 @@
          <el-input v-model="form.username" placeholder="请输入系统用户名" maxlength="30" />
        </el-form-item>
        <el-form-item v-if="!form.id" label="用户密码" prop="password">
          <el-input type="password" v-model="form.password" placeholder="请输入系统用户密码" maxlength="20" />
          <el-input v-model="form.password" type="password" placeholder="请输入系统用户密码" maxlength="20" />
        </el-form-item>
        <el-form-item label="租户套餐" prop="packageId">
          <el-select v-model="form.packageId" :disabled="!!form.tenantId" placeholder="请选择租户套餐" clearable style="width: 100%">
@@ -108,7 +108,7 @@
          </el-select>
        </el-form-item>
        <el-form-item label="过期时间" prop="expireTime">
          <el-date-picker clearable v-model="form.expireTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择过期时间">
          <el-date-picker v-model="form.expireTime" clearable type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择过期时间">
          </el-date-picker>
        </el-form-item>
        <el-form-item label="用户数量" prop="accountCount">
@@ -124,7 +124,7 @@
          <el-input v-model="form.licenseNumber" placeholder="请输入统一社会信用代码" />
        </el-form-item>
        <el-form-item label="企业简介" prop="intro">
          <el-input type="textarea" v-model="form.intro" placeholder="请输入企业简介" />
          <el-input v-model="form.intro" type="textarea" placeholder="请输入企业简介" />
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="form.remark" placeholder="请输入备注" />
@@ -182,8 +182,8 @@
  packageId: '',
  expireTime: '',
  accountCount: 0,
  status: '0',
}
  status: '0'
};
const data = reactive<PageData<TenantForm, TenantQuery>>({
  form: { ...initFormData },
  queryParams: {
@@ -195,17 +195,17 @@
    companyName: ''
  },
  rules: {
    id: [{ required: true, message: "id不能为空", trigger: "blur" }],
    tenantId: [{ required: true, message: "租户编号不能为空", trigger: "blur" }],
    contactUserName: [{ required: true, message: "联系人不能为空", trigger: "blur" }],
    contactPhone: [{ required: true, message: "联系电话不能为空", trigger: "blur" }],
    companyName: [{ required: true, message: "企业名称不能为空", trigger: "blur" }],
    id: [{ required: true, message: 'id不能为空', trigger: 'blur' }],
    tenantId: [{ required: true, message: '租户编号不能为空', trigger: 'blur' }],
    contactUserName: [{ required: true, message: '联系人不能为空', trigger: 'blur' }],
    contactPhone: [{ required: true, message: '联系电话不能为空', trigger: 'blur' }],
    companyName: [{ required: true, message: '企业名称不能为空', trigger: 'blur' }],
    username: [
      { required: true, message: "用户名不能为空", trigger: "blur" },
      { required: true, message: '用户名不能为空', trigger: 'blur' },
      { min: 2, max: 20, message: '用户名称长度必须介于 2 å’Œ 20 ä¹‹é—´', trigger: 'blur' }
    ],
    password: [
      { required: true, message: "密码不能为空", trigger: "blur" },
      { required: true, message: '密码不能为空', trigger: 'blur' },
      { min: 5, max: 20, message: '用户密码长度必须介于 5 å’Œ 20 ä¹‹é—´', trigger: 'blur' }
    ]
  }
@@ -215,9 +215,9 @@
/** æŸ¥è¯¢æ‰€æœ‰ç§Ÿæˆ·å¥—餐 */
const getTenantPackage = async () => {
  const res = await selectTenantPackage()
  const res = await selectTenantPackage();
  packageList.value = res.data;
}
};
/** æŸ¥è¯¢ç§Ÿæˆ·åˆ—表 */
const getList = async () => {
@@ -226,60 +226,58 @@
  tenantList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
};
// ç§Ÿæˆ·å¥—餐状态修改
const handleStatusChange = async (row: TenantVO) => {
  let text = row.status === "0" ? "启用" : "停用";
  let text = row.status === '0' ? '启用' : '停用';
  try {
    await proxy?.$modal.confirm('确认要"' + text + '""' + row.companyName + '"租户吗?');
    await changeTenantStatus(row.id, row.tenantId, row.status);
    proxy?.$modal.msgSuccess(text + "成功");
    proxy?.$modal.msgSuccess(text + '成功');
  } catch {
    row.status = row.status === "0" ? "1" : "0";
    row.status = row.status === '0' ? '1' : '0';
  }
}
};
// å–消按钮
const cancel = () => {
  reset();
  dialog.visible = false;
}
};
// è¡¨å•重置
const reset = () => {
  form.value = { ...initFormData };
  tenantFormRef.value?.resetFields();
}
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value?.resetFields();
  handleQuery();
}
};
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
const handleSelectionChange = (selection: TenantVO[]) => {
  ids.value = selection.map(item => item.id);
  ids.value = selection.map((item) => item.id);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
};
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
  reset();
  getTenantPackage();
  dialog.visible = true;
  dialog.title = "添加租户";
}
  dialog.title = '添加租户';
};
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = async (row?: TenantVO) => {
@@ -287,10 +285,10 @@
  await getTenantPackage();
  const _id = row?.id || ids.value[0];
  const res = await getTenant(_id);
  Object.assign(form.value, res.data)
  Object.assign(form.value, res.data);
  dialog.visible = true;
  dialog.title = "修改租户";
}
  dialog.title = '修改租户';
};
/** æäº¤æŒ‰é’® */
const submitForm = () => {
@@ -298,28 +296,26 @@
    if (valid) {
      buttonLoading.value = true;
      if (form.value.id) {
        await updateTenant(form.value).finally(() => buttonLoading.value = false);
        await updateTenant(form.value).finally(() => (buttonLoading.value = false));
      } else {
        await addTenant(form.value).finally(() => buttonLoading.value = false);
        await addTenant(form.value).finally(() => (buttonLoading.value = false));
      }
      proxy?.$modal.msgSuccess("操作成功");
      proxy?.$modal.msgSuccess('操作成功');
      dialog.visible = false;
      await getList();
    }
  });
}
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: TenantVO) => {
  const _ids = row?.id || ids.value;
  await proxy?.$modal.confirm('是否确认删除租户编号为"' + _ids + '"的数据项?')
  await proxy?.$modal.confirm('是否确认删除租户编号为"' + _ids + '"的数据项?');
  loading.value = true;
  await delTenant(_ids).finally(() => loading.value = false);
  await delTenant(_ids).finally(() => (loading.value = false));
  await getList();
  proxy?.$modal.msgSuccess("删除成功");
}
  proxy?.$modal.msgSuccess('删除成功');
};
/** åŒæ­¥ç§Ÿæˆ·å¥—餐按钮操作 */
const handleSyncTenantPackage = async (row: TenantVO) => {
@@ -328,20 +324,26 @@
    loading.value = true;
    await syncTenantPackage(row.tenantId, row.packageId);
    await getList();
    proxy?.$modal.msgSuccess("同步成功");
  } catch { return } finally {
    proxy?.$modal.msgSuccess('同步成功');
  } catch {
    return;
  } finally {
    loading.value = false;
  }
}
};
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
  proxy?.download('system/tenant/export', {
    ...queryParams.value
  }, `tenant_${new Date().getTime()}.xlsx`)
}
  proxy?.download(
    'system/tenant/export',
    {
      ...queryParams.value
    },
    `tenant_${new Date().getTime()}.xlsx`
  );
};
onMounted(() => {
  getList();
})
});
</script>
src/views/system/tenantPackage/index.vue
@@ -1,9 +1,9 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="mb-[10px]" v-show="showSearch">
      <div v-show="showSearch" class="mb-[10px]">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
            <el-form-item label="套餐名称" prop="packageName">
              <el-input v-model="queryParams.packageName" placeholder="请输入套餐名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
            </el-form-item>
@@ -20,28 +20,28 @@
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:tenantPackage:add']"> æ–°å¢ž </el-button>
            <el-button v-hasPermi="['system:tenantPackage:add']" type="primary" plain icon="Plus" @click="handleAdd"> æ–°å¢ž </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:tenantPackage:edit']">
            <el-button v-hasPermi="['system:tenantPackage:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">
              ä¿®æ”¹
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:tenantPackage:remove']">
            <el-button v-hasPermi="['system:tenantPackage:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
              åˆ é™¤
            </el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:tenantPackage:export']">导出 </el-button>
            <el-button v-hasPermi="['system:tenantPackage:export']" type="warning" plain icon="Download" @click="handleExport">导出 </el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
      <el-table v-loading="loading" :data="tenantPackageList" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="租户套餐id" align="center" prop="packageId" v-if="false" />
        <el-table-column v-if="false" label="租户套餐id" align="center" prop="packageId" />
        <el-table-column label="套餐名称" align="center" prop="packageName" />
        <el-table-column label="备注" align="center" prop="remark" />
        <el-table-column label="状态" align="center" prop="status">
@@ -52,20 +52,20 @@
        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="修改" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:tenantPackage:edit']"></el-button>
              <el-button v-hasPermi="['system:tenantPackage:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:tenantPackage:remove']"></el-button>
              <el-button v-hasPermi="['system:tenantPackage:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹ç§Ÿæˆ·å¥—é¤å¯¹è¯æ¡† -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
    <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
      <el-form ref="tenantPackageFormRef" :model="form" :rules="rules" label-width="80px">
        <el-form-item label="套餐名称" prop="packageName">
          <el-input v-model="form.packageName" placeholder="请输入套餐名称" />
@@ -75,10 +75,10 @@
          <el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选 </el-checkbox>
          <el-checkbox v-model="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')">父子联动 </el-checkbox>
          <el-tree
            ref="menuTreeRef"
            class="tree-border"
            :data="menuOptions"
            show-checkbox
            ref="menuTreeRef"
            node-key="id"
            :check-strictly="!form.menuCheckStrictly"
            empty-text="加载中,请稍候"
@@ -107,11 +107,11 @@
  addTenantPackage,
  updateTenantPackage,
  changePackageStatus
} from "@/api/system/tenantPackage";
import { treeselect as menuTreeselect, tenantPackageMenuTreeselect } from "@/api/system/menu";
import { TenantPkgForm, TenantPkgQuery, TenantPkgVO } from "@/api/system/tenantPackage/types";
import { MenuTreeOption } from "@/api/system/menu/types";
import to from "await-to-js";
} from '@/api/system/tenantPackage';
import { treeselect as menuTreeselect, tenantPackageMenuTreeselect } from '@/api/system/menu';
import { TenantPkgForm, TenantPkgQuery, TenantPkgVO } from '@/api/system/tenantPackage/types';
import { MenuTreeOption } from '@/api/system/menu/types';
import to from 'await-to-js';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -133,15 +133,14 @@
const dialog = reactive<DialogOption>({
  visible: false,
  title: ""
  title: ''
});
const initFormData: TenantPkgForm = {
  packageId: undefined,
  packageName: "",
  menuIds: "",
  remark: "",
  packageName: '',
  menuIds: '',
  remark: '',
  menuCheckStrictly: true
};
const data = reactive<PageData<TenantPkgForm, TenantPkgQuery>>({
@@ -149,11 +148,11 @@
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    packageName: ""
    packageName: ''
  },
  rules: {
    packageId: [{ required: true, message: "租户套餐id不能为空", trigger: "blur" }],
    packageName: [{ required: true, message: "套餐名称不能为空", trigger: "blur" }]
    packageId: [{ required: true, message: '租户套餐id不能为空', trigger: 'blur' }],
    packageName: [{ required: true, message: '套餐名称不能为空', trigger: 'blur' }]
  }
});
@@ -195,13 +194,13 @@
// ç§Ÿæˆ·å¥—餐状态修改
const handleStatusChange = async (row: TenantPkgVO) => {
  let text = row.status === "0" ? "启用" : "停用";
  const [err] = await to(proxy?.$modal.confirm("确认要\"" + text + "\"\"" + row.packageName + "\"套餐吗?") as Promise<any>);
  let text = row.status === '0' ? '启用' : '停用';
  const [err] = await to(proxy?.$modal.confirm('确认要"' + text + '""' + row.packageName + '"套餐吗?') as Promise<any>);
  if (err) {
    row.status = row.status === "0" ? "1" : "0";
    row.status = row.status === '0' ? '1' : '0';
  } else {
    await changePackageStatus(row.packageId, row.status);
    proxy?.$modal.msgSuccess(text + "成功");
    proxy?.$modal.msgSuccess(text + '成功');
  }
};
@@ -234,14 +233,14 @@
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
const handleSelectionChange = (selection: TenantPkgVO[]) => {
  ids.value = selection.map(item => item.packageId);
  ids.value = selection.map((item) => item.packageId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
};
// æ ‘权限(展开/折叠)
const handleCheckedTreeExpand = (value: CheckboxValueType, type: string) => {
  if (type == "menu") {
  if (type == 'menu') {
    let treeList = menuOptions.value;
    for (let i = 0; i < treeList.length; i++) {
      if (menuTreeRef.value) {
@@ -253,14 +252,14 @@
// æ ‘权限(全选/全不选)
const handleCheckedTreeNodeAll = (value: CheckboxValueType, type: string) => {
  if (type == "menu") {
    menuTreeRef.value?.setCheckedNodes(value ? menuOptions.value as any : []);
  if (type == 'menu') {
    menuTreeRef.value?.setCheckedNodes(value ? (menuOptions.value as any) : []);
  }
};
// æ ‘权限(父子联动)
const handleCheckedTreeConnect = (value: CheckboxValueType, type: string) => {
  if (type == "menu") {
  if (type == 'menu') {
    form.value.menuCheckStrictly = value as boolean;
  }
};
@@ -270,7 +269,7 @@
  reset();
  getMenuTreeselect();
  dialog.visible = true;
  dialog.title = "添加租户套餐";
  dialog.title = '添加租户套餐';
};
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
@@ -281,7 +280,7 @@
  form.value = response.data;
  const res = await getPackageMenuTreeselect(_packageId);
  dialog.visible = true;
  dialog.title = "修改租户套餐";
  dialog.title = '修改租户套餐';
  res.data.checkedKeys.forEach((v) => {
    nextTick(() => {
      menuTreeRef.value?.setChecked(v, true, false);
@@ -296,11 +295,11 @@
      buttonLoading.value = true;
      form.value.menuIds = getMenuAllCheckedKeys();
      if (form.value.packageId != null) {
        await updateTenantPackage(form.value).finally(() => buttonLoading.value = false);
        await updateTenantPackage(form.value).finally(() => (buttonLoading.value = false));
      } else {
        await addTenantPackage(form.value).finally(() => buttonLoading.value = false);
        await addTenantPackage(form.value).finally(() => (buttonLoading.value = false));
      }
      proxy?.$modal.msgSuccess("操作成功");
      proxy?.$modal.msgSuccess('操作成功');
      dialog.visible = false;
      await getList();
    }
@@ -310,20 +309,24 @@
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: TenantPkgVO) => {
  const _packageIds = row?.packageId || ids.value;
  await proxy?.$modal.confirm("是否确认删除租户套餐编号为\"" + _packageIds + "\"的数据项?").finally(() => {
  await proxy?.$modal.confirm('是否确认删除租户套餐编号为"' + _packageIds + '"的数据项?').finally(() => {
    loading.value = false;
  });
  await delTenantPackage(_packageIds);
  loading.value = true;
  await getList();
  proxy?.$modal.msgSuccess("删除成功");
  proxy?.$modal.msgSuccess('删除成功');
};
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
  proxy?.download("system/tenantPackage/export", {
    ...queryParams.value
  }, `tenantPackage_${new Date().getTime()}.xlsx`);
  proxy?.download(
    'system/tenantPackage/export',
    {
      ...queryParams.value
    },
    `tenantPackage_${new Date().getTime()}.xlsx`
  );
};
onMounted(() => {
src/views/system/user/authRole.vue
@@ -21,12 +21,12 @@
      <h4 class="panel-title">角色信息</h4>
      <div>
        <el-table
          ref="tableRef"
          v-loading="loading"
          :row-key="getRowKey"
          @row-click="clickRow"
          ref="tableRef"
          @selection-change="handleSelectionChange"
          :data="roles.slice((pageNum - 1) * pageSize, pageNum * pageSize)"
          @row-click="clickRow"
          @selection-change="handleSelectionChange"
        >
          <el-table-column label="序号" width="55" type="index" align="center">
            <template #default="scope">
@@ -43,8 +43,8 @@
            </template>
          </el-table-column>
        </el-table>
        <pagination v-show="total > 0" :total="total" v-model:page="pageNum" v-model:limit="pageSize" />
        <div style="text-align: center;margin-left:-120px;margin-top:30px;">
        <pagination v-show="total > 0" v-model:page="pageNum" v-model:limit="pageSize" :total="total" />
        <div style="text-align: center; margin-left: -120px; margin-top: 30px">
          <el-button type="primary" @click="submitForm()">提交</el-button>
          <el-button @click="close()">返回</el-button>
        </div>
@@ -55,9 +55,9 @@
</template>
<script setup name="AuthRole" lang="ts">
import { RoleVO } from "@/api/system/role/types";
import { getAuthRole, updateAuthRole } from "@/api/system/user";
import { UserForm } from "@/api/system/user/types";
import { RoleVO } from '@/api/system/role/types';
import { getAuthRole, updateAuthRole } from '@/api/system/user';
import { UserForm } from '@/api/system/user/types';
const route = useRoute();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -70,7 +70,7 @@
const roles = ref<RoleVO[]>([]);
const form = ref<Partial<UserForm>>({
  nickName: undefined,
  userName: "",
  userName: '',
  userId: undefined
});
@@ -83,7 +83,7 @@
};
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: RoleVO[]) => {
  roleIds.value = selection.map(item => item.roleId);
  roleIds.value = selection.map((item) => item.roleId);
};
/** ä¿å­˜é€‰ä¸­çš„æ•°æ®ç¼–号 */
const getRowKey = (row: RoleVO): string => {
@@ -91,15 +91,15 @@
};
/** å…³é—­æŒ‰é’® */
const close = () => {
  const obj = { path: "/system/user" };
  const obj = { path: '/system/user' };
  proxy?.$tab.closeOpenPage(obj);
};
/** æäº¤æŒ‰é’® */
const submitForm = async () => {
  const userId = form.value.userId;
  const rIds = roleIds.value.join(",");
  const rIds = roleIds.value.join(',');
  await updateAuthRole({ userId: userId as string, roleIds: rIds });
  proxy?.$modal.msgSuccess("授权成功");
  proxy?.$modal.msgSuccess('授权成功');
  close();
};
@@ -112,7 +112,7 @@
    Object.assign(roles.value, res.data.roles);
    total.value = roles.value.length;
    await nextTick(() => {
      roles.value.forEach(row => {
      roles.value.forEach((row) => {
        if (row?.flag) {
          tableRef.value?.toggleRowSelection(row, true);
        }
src/views/system/user/index.vue
@@ -6,8 +6,8 @@
        <el-card shadow="hover">
          <el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable />
          <el-tree
            class="mt-2"
            ref="deptTreeRef"
            class="mt-2"
            node-key="id"
            :data="deptOptions"
            :props="{ label: 'label', children: 'children' }"
@@ -21,7 +21,7 @@
      </el-col>
      <el-col :lg="20" :xs="24">
        <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
          <div class="mb-[10px]" v-show="showSearch">
          <div v-show="showSearch" class="mb-[10px]">
            <el-card shadow="hover">
              <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
                <el-form-item label="用户名称" prop="userName">
@@ -42,7 +42,7 @@
                    <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
                  </el-select>
                </el-form-item>
                <el-form-item label="创建时间" style="width: 308px;">
                <el-form-item label="创建时间" style="width: 308px">
                  <el-date-picker
                    v-model="dateRange"
                    value-format="YYYY-MM-DD"
@@ -53,8 +53,8 @@
                  ></el-date-picker>
                </el-form-item>
                <el-form-item>
                  <el-button type="primary" @click="handleQuery" icon="Search">搜索</el-button>
                  <el-button @click="resetQuery" icon="Refresh">重置</el-button>
                  <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
                  <el-button icon="Refresh" @click="resetQuery">重置</el-button>
                </el-form-item>
              </el-form>
            </el-card>
@@ -65,15 +65,15 @@
          <template #header>
            <el-row :gutter="10">
              <el-col :span="1.5">
                <el-button type="primary" plain @click="handleAdd()" v-has-permi="['system:user:add']" icon="Plus">新增</el-button>
                <el-button v-has-permi="['system:user:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
              </el-col>
              <el-col :span="1.5">
                <el-button type="success" plain @click="handleUpdate()" :disabled="single" v-has-permi="['system:user:add']" icon="Edit">
                <el-button v-has-permi="['system:user:add']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">
                  ä¿®æ”¹
                </el-button>
              </el-col>
              <el-col :span="1.5">
                <el-button type="danger" plain @click="handleDelete()" :disabled="multiple" v-has-permi="['system:user:delete']" icon="Delete">
                <el-button v-has-permi="['system:user:delete']" type="danger" plain :disabled="multiple" icon="Delete" @click="handleDelete()">
                  åˆ é™¤
                </el-button>
              </el-col>
@@ -85,38 +85,38 @@
                  ></el-button>
                  <template #dropdown>
                    <el-dropdown-menu>
                      <el-dropdown-item @click="importTemplate" icon="Download">下载模板</el-dropdown-item>
                      <el-dropdown-item @click="handleImport" icon="Top"> å¯¼å…¥æ•°æ®</el-dropdown-item>
                      <el-dropdown-item @click="handleExport" icon="Download"> å¯¼å‡ºæ•°æ®</el-dropdown-item>
                      <el-dropdown-item icon="Download" @click="importTemplate">下载模板</el-dropdown-item>
                      <el-dropdown-item icon="Top" @click="handleImport"> å¯¼å…¥æ•°æ®</el-dropdown-item>
                      <el-dropdown-item icon="Download" @click="handleExport"> å¯¼å‡ºæ•°æ®</el-dropdown-item>
                    </el-dropdown-menu>
                  </template>
                </el-dropdown>
              </el-col>
              <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns" :search="true"></right-toolbar>
              <right-toolbar v-model:showSearch="showSearch" :columns="columns" :search="true" @query-table="getList"></right-toolbar>
            </el-row>
          </template>
          <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
            <el-table-column type="selection" width="50" align="center" />
            <el-table-column label="用户编号" align="center" key="userId" prop="userId" v-if="columns[0].visible" />
            <el-table-column label="用户名称" align="center" key="userName" prop="userName" v-if="columns[1].visible" :show-overflow-tooltip="true" />
            <el-table-column label="用户昵称" align="center" key="nickName" prop="nickName" v-if="columns[2].visible" :show-overflow-tooltip="true" />
            <el-table-column v-if="columns[0].visible" key="userId" label="用户编号" align="center" prop="userId" />
            <el-table-column v-if="columns[1].visible" key="userName" label="用户名称" align="center" prop="userName" :show-overflow-tooltip="true" />
            <el-table-column v-if="columns[2].visible" key="nickName" label="用户昵称" align="center" prop="nickName" :show-overflow-tooltip="true" />
            <el-table-column
              v-if="columns[3].visible"
              key="deptName"
              label="部门"
              align="center"
              key="deptName"
              prop="dept.deptName"
              v-if="columns[3].visible"
              :show-overflow-tooltip="true"
            />
            <el-table-column label="手机号码" align="center" key="phonenumber" prop="phonenumber" v-if="columns[4].visible" width="120" />
            <el-table-column label="状态" align="center" key="status" v-if="columns[5].visible">
            <el-table-column v-if="columns[4].visible" key="phonenumber" label="手机号码" align="center" prop="phonenumber" width="120" />
            <el-table-column v-if="columns[5].visible" key="status" label="状态" align="center">
              <template #default="scope">
                <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" v-if="columns[6].visible" width="160">
            <el-table-column v-if="columns[6].visible" label="创建时间" align="center" prop="createTime" width="160">
              <template #default="scope">
                <span>{{ scope.row.createTime }}</span>
              </template>
@@ -124,19 +124,19 @@
            <el-table-column label="操作" fixed="right" width="180" class-name="small-padding fixed-width">
              <template #default="scope">
                <el-tooltip content="修改" placement="top" v-if="scope.row.userId !== 1">
                  <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:user:edit']"></el-button>
                <el-tooltip v-if="scope.row.userId !== 1" content="修改" placement="top">
                  <el-button v-hasPermi="['system:user:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
                </el-tooltip>
                <el-tooltip content="删除" placement="top" v-if="scope.row.userId !== 1">
                  <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:user:remove']"></el-button>
                <el-tooltip v-if="scope.row.userId !== 1" content="删除" placement="top">
                  <el-button v-hasPermi="['system:user:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
                </el-tooltip>
                <el-tooltip content="重置密码" placement="top" v-if="scope.row.userId !== 1">
                  <el-button link type="primary" icon="Key" @click="handleResetPwd(scope.row)" v-hasPermi="['system:user:resetPwd']"></el-button>
                <el-tooltip v-if="scope.row.userId !== 1" content="重置密码" placement="top">
                  <el-button v-hasPermi="['system:user:resetPwd']" link type="primary" icon="Key" @click="handleResetPwd(scope.row)"></el-button>
                </el-tooltip>
                <el-tooltip content="分配角色" placement="top" v-if="scope.row.userId !== 1">
                  <el-button link type="primary" icon="CircleCheck" @click="handleAuthRole(scope.row)" v-hasPermi="['system:user:edit']"></el-button>
                <el-tooltip v-if="scope.row.userId !== 1" content="分配角色" placement="top">
                  <el-button v-hasPermi="['system:user:edit']" link type="primary" icon="CircleCheck" @click="handleAuthRole(scope.row)"></el-button>
                </el-tooltip>
              </template>
            </el-table-column>
@@ -144,9 +144,9 @@
          <pagination
            v-show="total > 0"
            :total="total"
            v-model:page="queryParams.pageNum"
            v-model:limit="queryParams.pageSize"
            :total="total"
            @pagination="getList"
          />
        </el-card>
@@ -154,8 +154,8 @@
    </el-row>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹ç”¨æˆ·é…ç½®å¯¹è¯æ¡† -->
    <el-dialog ref="formDialogRef" :title="dialog.title" v-model="dialog.visible" width="600px" append-to-body @close="closeDialog">
      <el-form :model="form" :rules="rules" ref="userFormRef" label-width="80px">
    <el-dialog ref="formDialogRef" v-model="dialog.visible" :title="dialog.title" width="600px" append-to-body @close="closeDialog">
      <el-form ref="userFormRef" :model="form" :rules="rules" label-width="80px">
        <el-row>
          <el-col :span="12">
            <el-form-item label="用户昵称" prop="nickName">
@@ -210,8 +210,7 @@
          <el-col :span="12">
            <el-form-item label="状态">
              <el-radio-group v-model="form.status">
                <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">{{
                  dict.label }}</el-radio>
                <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
@@ -261,7 +260,7 @@
    </el-dialog>
    <!-- ç”¨æˆ·å¯¼å…¥å¯¹è¯æ¡† -->
    <el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
    <el-dialog v-model="upload.open" :title="upload.title" width="400px" append-to-body>
      <el-upload
        ref="uploadRef"
        :limit="1"
@@ -282,7 +281,7 @@
          <div class="text-center el-upload__tip">
            <div class="el-upload__tip"><el-checkbox v-model="upload.updateSupport" />是否更新已经存在的用户数据</div>
            <span>仅允许导入xls、xlsx格式文件。</span>
            <el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
            <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板</el-link>
          </div>
        </template>
      </el-upload>
@@ -297,22 +296,22 @@
</template>
<script setup name="User" lang="ts">
import api from "@/api/system/user"
import api from '@/api/system/user';
import { UserForm, UserQuery, UserVO } from '@/api/system/user/types';
import { treeselect } from "@/api/system/dept";
import { DeptVO } from "@/api/system/dept/types";
import { RoleVO } from "@/api/system/role/types";
import { PostVO } from "@/api/system/post/types";
import { to } from "await-to-js";
import { globalHeaders } from "@/utils/request";
import { treeselect } from '@/api/system/dept';
import { DeptVO } from '@/api/system/dept/types';
import { RoleVO } from '@/api/system/role/types';
import { PostVO } from '@/api/system/post/types';
import { to } from 'await-to-js';
import { globalHeaders } from '@/utils/request';
const router = useRouter();
const { proxy } = getCurrentInstance() as ComponentInternalInstance
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable, sys_user_sex } = toRefs<any>(proxy?.useDict('sys_normal_disable', 'sys_user_sex'));
const userList = ref<UserVO[]>();
const loading = ref(true);
const showSearch = ref(true)
const showSearch = ref(true);
const ids = ref<Array<number | string>>([]);
const single = ref(true);
const multiple = ref(true);
@@ -320,7 +319,7 @@
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const deptName = ref('');
const deptOptions = ref<DeptVO[]>([]);
const initPassword = ref<String>('');
const initPassword = ref<string>('');
const postOptions = ref<PostVO[]>([]);
const roleOptions = ref<RoleVO[]>([]);
/*** ç”¨æˆ·å¯¼å…¥å‚æ•° */
@@ -328,7 +327,7 @@
  // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚(用户导入)
  open: false,
  // å¼¹å‡ºå±‚标题(用户导入)
  title: "",
  title: '',
  // æ˜¯å¦ç¦ç”¨ä¸Šä¼ 
  isUploading: false,
  // æ˜¯å¦æ›´æ–°å·²ç»å­˜åœ¨çš„用户数据
@@ -336,19 +335,18 @@
  // è®¾ç½®ä¸Šä¼ çš„请求头部
  headers: globalHeaders(),
  // ä¸Šä¼ çš„地址
  url: import.meta.env.VITE_APP_BASE_API + "/system/user/importData"
})
  url: import.meta.env.VITE_APP_BASE_API + '/system/user/importData'
});
// åˆ—显隐信息
const columns = ref<FieldOption[]>([
  { key: 0, label: `用户编号`, visible: false,children: [] },
  { key: 1, label: `用户名称`, visible: true,children: [] },
  { key: 2, label: `用户昵称`, visible: true,children: [] },
  { key: 3, label: `部门`, visible: true,children: [] },
  { key: 4, label: `手机号码`, visible: true,children: [] },
  { key: 5, label: `状态`, visible: true,children: [] },
  { key: 6, label: `创建时间`, visible: true,children: [] }
])
  { key: 0, label: `用户编号`, visible: false, children: [] },
  { key: 1, label: `用户名称`, visible: true, children: [] },
  { key: 2, label: `用户昵称`, visible: true, children: [] },
  { key: 3, label: `部门`, visible: true, children: [] },
  { key: 4, label: `手机号码`, visible: true, children: [] },
  { key: 5, label: `状态`, visible: true, children: [] },
  { key: 6, label: `创建时间`, visible: true, children: [] }
]);
const deptTreeRef = ref<ElTreeInstance>();
const queryFormRef = ref<ElFormInstance>();
@@ -370,11 +368,11 @@
  phonenumber: undefined,
  email: undefined,
  sex: undefined,
  status: "0",
  status: '0',
  remark: '',
  postIds: [],
  roleIds: []
}
};
const data = reactive<PageData<UserForm, UserQuery>>({
  form: { ...initFormData },
  queryParams: {
@@ -386,24 +384,54 @@
    deptId: ''
  },
  rules: {
    userName: [{ required: true, message: "用户名称不能为空", trigger: "blur" }, { min: 2, max: 20, message: "用户名称长度必须介于 2 å’Œ 20 ä¹‹é—´", trigger: "blur" }],
    nickName: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }],
    password: [{ required: true, message: "用户密码不能为空", trigger: "blur" }, { min: 5, max: 20, message: "用户密码长度必须介于 5 å’Œ 20 ä¹‹é—´", trigger: "blur" }],
    email: [{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }],
    phonenumber: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }]
    userName: [
      { required: true, message: '用户名称不能为空', trigger: 'blur' },
      {
        min: 2,
        max: 20,
        message: '用户名称长度必须介于 2 å’Œ 20 ä¹‹é—´',
        trigger: 'blur'
      }
    ],
    nickName: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }],
    password: [
      { required: true, message: '用户密码不能为空', trigger: 'blur' },
      {
        min: 5,
        max: 20,
        message: '用户密码长度必须介于 5 å’Œ 20 ä¹‹é—´',
        trigger: 'blur'
      }
    ],
    email: [
      {
        type: 'email',
        message: '请输入正确的邮箱地址',
        trigger: ['blur', 'change']
      }
    ],
    phonenumber: [
      {
        pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
        message: '请输入正确的手机号码',
        trigger: 'blur'
      }
    ]
  }
})
});
const { queryParams, form, rules } = toRefs<PageData<UserForm, UserQuery>>(data)
const { queryParams, form, rules } = toRefs<PageData<UserForm, UserQuery>>(data);
/** é€šè¿‡æ¡ä»¶è¿‡æ»¤èŠ‚ç‚¹  */
const filterNode = (value: string, data: any) => {
  if (!value) return true
  return data.label.indexOf(value) !== -1
}
  if (!value) return true;
  return data.label.indexOf(value) !== -1;
};
/** æ ¹æ®åç§°ç­›é€‰éƒ¨é—¨æ ‘ */
watchEffect(
  () => { deptTreeRef.value?.filter(deptName.value); },
  () => {
    deptTreeRef.value?.filter(deptName.value);
  },
  {
    flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发,此属性控制在DOM元素更新后运行
  }
@@ -422,29 +450,28 @@
  loading.value = false;
  userList.value = res.rows;
  total.value = res.total;
}
};
/** èŠ‚ç‚¹å•å‡»äº‹ä»¶ */
const handleNodeClick = (data: DeptVO) => {
  queryParams.value.deptId = data.id;
  handleQuery()
}
  handleQuery();
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1
  getList()
}
  queryParams.value.pageNum = 1;
  getList();
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  dateRange.value = ['', '']
  dateRange.value = ['', ''];
  queryFormRef.value?.resetFields();
  queryParams.value.pageNum = 1;
  queryParams.value.deptId = undefined;
  deptTreeRef.value?.setCurrentKey(undefined);
  handleQuery();
}
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: UserVO) => {
@@ -453,78 +480,85 @@
  if (!err) {
    await api.delUser(userIds);
    await getList();
    proxy?.$modal.msgSuccess("删除成功");
    proxy?.$modal.msgSuccess('删除成功');
  }
}
};
/** ç”¨æˆ·çŠ¶æ€ä¿®æ”¹  */
const handleStatusChange = async (row: UserVO) => {
  let text = row.status === "0" ? "启用" : "停用"
  let text = row.status === '0' ? '启用' : '停用';
  try {
    await proxy?.$modal.confirm('确认要"' + text + '""' + row.userName + '"用户吗?');
    await api.changeUserStatus(row.userId, row.status);
    proxy?.$modal.msgSuccess(text + "成功");
    proxy?.$modal.msgSuccess(text + '成功');
  } catch (err) {
    row.status = row.status === "0" ? "1" : "0";
    row.status = row.status === '0' ? '1' : '0';
  }
}
};
/** è·³è½¬è§’色分配 */
const handleAuthRole = (row: UserVO) => {
  const userId = row.userId;
  router.push("/system/user-auth/role/" + userId);
}
  router.push('/system/user-auth/role/' + userId);
};
/** é‡ç½®å¯†ç æŒ‰é’®æ“ä½œ */
const handleResetPwd = async (row: UserVO) => {
  const [err, res] = await to(ElMessageBox.prompt('请输入"' + row.userName + '"的新密码', "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    closeOnClickModal: false,
    inputPattern: /^.{5,20}$/,
    inputErrorMessage: "用户密码长度必须介于 5 å’Œ 20 ä¹‹é—´",
  }))
  const [err, res] = await to(
    ElMessageBox.prompt('请输入"' + row.userName + '"的新密码', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      closeOnClickModal: false,
      inputPattern: /^.{5,20}$/,
      inputErrorMessage: '用户密码长度必须介于 5 å’Œ 20 ä¹‹é—´'
    })
  );
  if (!err) {
    await api.resetUserPwd(row.userId, res.value);
    proxy?.$modal.msgSuccess("修改成功,新密码是:" + res.value);
    proxy?.$modal.msgSuccess('修改成功,新密码是:' + res.value);
  }
}
};
/** é€‰æ‹©æ¡æ•°  */
const handleSelectionChange = (selection: UserVO[]) => {
  ids.value = selection.map((item) => item.userId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
};
/** å¯¼å…¥æŒ‰é’®æ“ä½œ */
const handleImport = () => {
  upload.title = "用户导入";
  upload.title = '用户导入';
  upload.open = true;
}
};
/** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
const handleExport = () => {
  proxy?.download("system/user/export", {
    ...queryParams.value,
  }, `user_${new Date().getTime()}.xlsx`);
  proxy?.download(
    'system/user/export',
    {
      ...queryParams.value
    },
    `user_${new Date().getTime()}.xlsx`
  );
};
/** ä¸‹è½½æ¨¡æ¿æ“ä½œ */
const importTemplate = () => {
  proxy?.download("system/user/importTemplate", {
  }, `user_template_${new Date().getTime()}.xlsx`);
}
  proxy?.download('system/user/importTemplate', {}, `user_template_${new Date().getTime()}.xlsx`);
};
/**文件上传中处理 */
const handleFileUploadProgress = () => {
  upload.isUploading = true;
}
};
/** æ–‡ä»¶ä¸Šä¼ æˆåŠŸå¤„ç† */
const handleFileSuccess = (response: any, file: UploadFile) => {
  upload.open = false;
  upload.isUploading = false;
  uploadRef.value?.handleRemove(file);
  ElMessageBox.alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true });
  ElMessageBox.alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + '</div>', '导入结果', {
    dangerouslyUseHTMLString: true
  });
  getList();
}
};
/** æäº¤ä¸Šä¼ æ–‡ä»¶ */
function submitFileForm() {
@@ -538,59 +572,57 @@
    const { data } = await treeselect();
    deptOptions.value = data;
  }
}
};
/** é‡ç½®æ“ä½œè¡¨å• */
const reset = () => {
  form.value = { ...initFormData };
  userFormRef.value?.resetFields();
}
};
/** å–消按钮 */
const cancel = () => {
  dialog.visible = false;
  reset();
}
};
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = async () => {
  reset();
  const { data } = await api.getUser();
  dialog.visible = true;
  dialog.title = "新增用户";
  dialog.title = '新增用户';
  await initTreeData();
  postOptions.value = data.posts;
  roleOptions.value = data.roles;
  form.value.password = initPassword.value.toString();
}
};
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = async (row?: UserForm) => {
  reset();
  const userId = row?.userId || ids.value[0]
  const { data } = await api.getUser(userId)
  const userId = row?.userId || ids.value[0];
  const { data } = await api.getUser(userId);
  dialog.visible = true;
  dialog.title = "修改用户";
  dialog.title = '修改用户';
  await initTreeData();
  Object.assign(form.value, data.user);
  postOptions.value = data.posts;
  roleOptions.value = data.roles;
  form.value.postIds = data.postIds;
  form.value.roleIds = data.roleIds;
  form.value.password = "";
}
  form.value.password = '';
};
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  userFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      form.value.userId ? await api.updateUser(form.value) : await api.addUser(form.value);
      proxy?.$modal.msgSuccess("操作成功");
      proxy?.$modal.msgSuccess('操作成功');
      dialog.visible = false;
      await getList();
    }
  })
}
  });
};
/**
 * å…³é—­ç”¨æˆ·å¼¹çª—
@@ -598,7 +630,7 @@
const closeDialog = () => {
  dialog.visible = false;
  resetForm();
}
};
/**
 * é‡ç½®è¡¨å•
@@ -609,11 +641,11 @@
  form.value.id = undefined;
  form.value.status = '1';
}
};
onMounted(() => {
  getTreeSelect() // åˆå§‹åŒ–部门数据
  getList() // åˆå§‹åŒ–列表数据
  proxy?.getConfigKey("sys.user.initPassword").then(response => {
  getTreeSelect(); // åˆå§‹åŒ–部门数据
  getList(); // åˆå§‹åŒ–列表数据
  proxy?.getConfigKey('sys.user.initPassword').then((response) => {
    initPassword.value = response.data;
  });
});
src/views/system/user/profile/index.vue
@@ -3,14 +3,14 @@
    <el-row :gutter="20">
      <el-col :span="6" :xs="24">
        <el-card class="box-card">
          <template v-slot:header>
          <template #header>
            <div class="clearfix">
              <span>个人信息</span>
            </div>
          </template>
          <div>
            <div class="text-center">
              <userAvatar/>
              <userAvatar />
            </div>
            <ul class="list-group list-group-striped">
              <li class="list-group-item">
@@ -27,7 +27,7 @@
              </li>
              <li class="list-group-item">
                <svg-icon icon-class="tree" />所属部门
                <div class="pull-right" v-if="state.user.dept">{{ state.user.dept.deptName }} / {{ state.postGroup }}</div>
                <div v-if="state.user.dept" class="pull-right">{{ state.user.dept.deptName }} / {{ state.postGroup }}</div>
              </li>
              <li class="list-group-item">
                <svg-icon icon-class="peoples" />所属角色
@@ -43,7 +43,7 @@
      </el-col>
      <el-col :span="18" :xs="24">
        <el-card>
          <template v-slot:header>
          <template #header>
            <div class="clearfix">
              <span>基本资料</span>
            </div>
@@ -66,38 +66,38 @@
</template>
<script setup name="Profile" lang="ts">
import UserAvatar from "./userAvatar.vue";
import UserInfo from "./userInfo.vue";
import ResetPwd from "./resetPwd.vue";
import ThirdParty from "./thirdParty.vue";
import { getAuthList } from "@/api/system/social/auth";
import { getUserProfile } from "@/api/system/user";
import UserAvatar from './userAvatar.vue';
import UserInfo from './userInfo.vue';
import ResetPwd from './resetPwd.vue';
import ThirdParty from './thirdParty.vue';
import { getAuthList } from '@/api/system/social/auth';
import { getUserProfile } from '@/api/system/user';
const activeTab = ref("userinfo");
const activeTab = ref('userinfo');
const state = ref<Record<string, any>>({
    user: {},
    roleGroup: '',
    postGroup: '',
    auths: []
  user: {},
  roleGroup: '',
  postGroup: '',
  auths: []
});
const userForm = ref({});
const getUser = async () => {
    const res = await getUserProfile();
    state.value.user = res.data.user;
    userForm.value = { ...res.data.user }
    state.value.roleGroup = res.data.roleGroup;
    state.value.postGroup = res.data.postGroup;
  const res = await getUserProfile();
  state.value.user = res.data.user;
  userForm.value = { ...res.data.user };
  state.value.roleGroup = res.data.roleGroup;
  state.value.postGroup = res.data.postGroup;
};
const getAuths = async () => {
    const res = await getAuthList();
    state.value.auths = res.data;
  const res = await getAuthList();
  state.value.auths = res.data;
};
onMounted(() => {
    getUser();
    getAuths();
})
  getUser();
  getAuths();
});
</script>
src/views/system/user/profile/resetPwd.vue
@@ -17,37 +17,43 @@
</template>
<script setup lang="ts">
import { updateUserPwd } from "@/api/system/user";
import type { ResetPwdForm } from "@/api/system/user/types";
import { updateUserPwd } from '@/api/system/user';
import type { ResetPwdForm } from '@/api/system/user/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const pwdRef = ref<ElFormInstance>();
const user = ref<ResetPwdForm>({
  oldPassword: "",
  newPassword: "",
  confirmPassword: ""
  oldPassword: '',
  newPassword: '',
  confirmPassword: ''
});
const equalToPassword = (rule: any, value: string, callback: any) => {
  if (user.value.newPassword !== value) {
    callback(new Error("两次输入的密码不一致"));
    callback(new Error('两次输入的密码不一致'));
  } else {
    callback();
  }
};
const rules = ref({
  oldPassword: [{ required: true, message: "旧密码不能为空", trigger: "blur" }],
  newPassword: [{ required: true, message: "新密码不能为空", trigger: "blur" }, {
    min: 6,
    max: 20,
    message: "长度在 6 åˆ° 20 ä¸ªå­—符",
    trigger: "blur"
  }],
  confirmPassword: [{ required: true, message: "确认密码不能为空", trigger: "blur" }, {
    required: true,
    validator: equalToPassword,
    trigger: "blur"
  }]
  oldPassword: [{ required: true, message: '旧密码不能为空', trigger: 'blur' }],
  newPassword: [
    { required: true, message: '新密码不能为空', trigger: 'blur' },
    {
      min: 6,
      max: 20,
      message: '长度在 6 åˆ° 20 ä¸ªå­—符',
      trigger: 'blur'
    }
  ],
  confirmPassword: [
    { required: true, message: '确认密码不能为空', trigger: 'blur' },
    {
      required: true,
      validator: equalToPassword,
      trigger: 'blur'
    }
  ]
});
/** æäº¤æŒ‰é’® */
@@ -55,7 +61,7 @@
  pwdRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      await updateUserPwd(user.value.oldPassword, user.value.newPassword);
      proxy?.$modal.msgSuccess("修改成功");
      proxy?.$modal.msgSuccess('修改成功');
    }
  });
};
src/views/system/user/profile/thirdParty.vue
@@ -4,14 +4,14 @@
      <el-table-column label="序号" width="50" type="index"></el-table-column>
      <el-table-column label="绑定账号平台" width="140" align="center" prop="source" show-overflow-tooltip />
      <el-table-column label="头像" width="120" align="center" prop="avatar">
        <template v-slot="scope">
        <template #default="scope">
          <img :src="scope.row.avatar" style="width: 45px; height: 45px" />
        </template>
      </el-table-column>
      <el-table-column label="系统账号" width="180" align="center" prop="userName" :show-overflow-tooltip="true" />
      <el-table-column label="绑定时间" width="180" align="center" prop="createTime" />
      <el-table-column label="操作" width="80" align="center" class-name="small-padding fixed-width">
        <template v-slot="scope">
        <template #default="scope">
          <el-button size="small" type="text" @click="unlockAuth(scope.row)">解绑</el-button>
        </template>
      </el-table-column>
@@ -20,25 +20,25 @@
    <div id="git-user-binding">
      <h4 class="provider-desc">你可以绑定以下第三方帐号</h4>
      <div id="authlist" class="user-bind">
        <a class="third-app" href="#" @click="authUrl('wechat');" title="使用 å¾®ä¿¡ è´¦å·æŽˆæƒç™»å½•">
        <a class="third-app" href="#" title="使用 å¾®ä¿¡ è´¦å·æŽˆæƒç™»å½•" @click="authUrl('wechat')">
          <div class="git-other-login-icon">
            <svg-icon icon-class="wechat" />
          </div>
          <span class="app-name">WeiXin</span>
        </a>
        <a class="third-app" href="#" @click="authUrl('maxkey');" title="使用 MaxKey è´¦å·æŽˆæƒç™»å½•">
        <a class="third-app" href="#" title="使用 MaxKey è´¦å·æŽˆæƒç™»å½•" @click="authUrl('maxkey')">
          <div class="git-other-login-icon">
            <svg-icon icon-class="maxkey" />
          </div>
          <span class="app-name">MaxKey</span>
        </a>
        <a class="third-app" href="#" @click="authUrl('gitee');" title="使用 Gitee è´¦å·æŽˆæƒç™»å½•">
        <a class="third-app" href="#" title="使用 Gitee è´¦å·æŽˆæƒç™»å½•" @click="authUrl('gitee')">
          <div class="git-other-login-icon">
            <svg-icon icon-class="gitee" />
          </div>
          <span class="app-name">Gitee</span>
        </a>
        <a class="third-app" href="#" @click="authUrl('github');" title="使用 GitHub è´¦å·æŽˆæƒç™»å½•">
        <a class="third-app" href="#" title="使用 GitHub è´¦å·æŽˆæƒç™»å½•" @click="authUrl('github')">
          <div class="git-other-login-icon">
            <svg-icon icon-class="github" />
          </div>
@@ -50,31 +50,32 @@
</template>
<script lang="ts" setup>
import { authUnlock, authBinding } from "@/api/system/social/auth";
import { PropType } from "vue";
import { authUnlock, authBinding } from '@/api/system/social/auth';
import { PropType } from 'vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const props = defineProps({
  auths: {
    type: Object as PropType<any>,
    type: Object as PropType<any>
  }
});
const auths = computed(() => props.auths);
const unlockAuth = (row: any) => {
  ElMessageBox.confirm('您确定要解除"' + row.source + '"的账号绑定吗?')
    .then(() => {
      return authUnlock(row.id);
    }).then((res: any) => {
    })
    .then((res: any) => {
      if (res.code === 200) {
        proxy?.$modal.msgSuccess("解绑成功");
        proxy?.$modal.msgSuccess('解绑成功');
        proxy?.$tab.refreshPage();
      } else {
        proxy?.$modal.msgError(res.msg);
      }
    }).catch(() => { });
    })
    .catch(() => {});
};
const authUrl = (source: string) => {
@@ -111,7 +112,7 @@
  margin-top: 10px;
}
.git-other-login-icon>img {
.git-other-login-icon > img {
  height: 32px;
}
@@ -122,15 +123,13 @@
}
.provider-desc {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial,
    "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Liberation Sans",
    "PingFang SC", "Microsoft YaHei", "Hiragino Sans GB", "Wenquanyi Micro Hei",
    "WenQuanYi Zen Hei", "ST Heiti", SimHei, SimSun, "WenQuanYi Zen Hei Sharp",
    sans-serif;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
    'Liberation Sans', 'PingFang SC', 'Microsoft YaHei', 'Hiragino Sans GB', 'Wenquanyi Micro Hei', 'WenQuanYi Zen Hei', 'ST Heiti', SimHei, SimSun,
    'WenQuanYi Zen Hei Sharp', sans-serif;
  font-size: 1.071rem;
}
td>img {
td > img {
  height: 20px;
  width: 20px;
  display: inline-block;
src/views/system/user/profile/userAvatar.vue
@@ -5,16 +5,16 @@
      <el-row>
        <el-col :xs="24" :md="12" :style="{ height: '350px' }">
          <vue-cropper
            v-if="visible"
            ref="cropper"
            :img="options.img"
            :info="true"
            :autoCrop="options.autoCrop"
            :autoCropWidth="options.autoCropWidth"
            :autoCropHeight="options.autoCropHeight"
            :fixedBox="options.fixedBox"
            :outputType="options.outputType"
            @realTime="realTime"
            v-if="visible"
            :auto-crop="options.autoCrop"
            :auto-crop-width="options.autoCropWidth"
            :auto-crop-height="options.autoCropHeight"
            :fixed-box="options.fixedBox"
            :output-type="options.outputType"
            @real-time="realTime"
          />
        </el-col>
        <el-col :xs="24" :md="12" :style="{ height: '350px' }">
@@ -56,10 +56,10 @@
</template>
<script setup lang="ts">
import "vue-cropper/dist/index.css";
import { VueCropper } from "vue-cropper";
import { uploadAvatar } from "@/api/system/user";
import useUserStore from "@/store/modules/user";
import 'vue-cropper/dist/index.css';
import { VueCropper } from 'vue-cropper';
import { uploadAvatar } from '@/api/system/user';
import useUserStore from '@/store/modules/user';
interface Options {
  img: string | any; // è£å‰ªå›¾ç‰‡çš„地址
@@ -73,13 +73,12 @@
  visible: boolean;
}
const userStore = useUserStore();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const open = ref(false);
const visible = ref(false);
const title = ref("修改头像");
const title = ref('修改头像');
const cropper = ref<any>({});
//图片裁剪数据
@@ -89,8 +88,8 @@
  autoCropWidth: 200,
  autoCropHeight: 200,
  fixedBox: true,
  outputType: "png",
  fileName: "",
  outputType: 'png',
  fileName: '',
  previews: {},
  visible: false
});
@@ -104,8 +103,7 @@
  visible.value = true;
};
/** è¦†ç›–默认上传行为 */
const requestUpload = (): any => {
};
const requestUpload = (): any => {};
/** å‘左旋转 */
const rotateLeft = () => {
  cropper.value.rotateLeft();
@@ -121,8 +119,8 @@
};
/** ä¸Šä¼ é¢„处理 */
const beforeUpload = (file: any) => {
  if (file.type.indexOf("image/") == -1) {
    proxy?.$modal.msgError("文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。");
  if (file.type.indexOf('image/') == -1) {
    proxy?.$modal.msgError('文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。');
  } else {
    const reader = new FileReader();
    reader.readAsDataURL(file);
@@ -136,7 +134,7 @@
const uploadImg = async () => {
  cropper.value.getCropBlob(async (data: any) => {
    let formData = new FormData();
    formData.append("avatarfile", data, options.fileName);
    formData.append('avatarfile', data, options.fileName);
    const res = await uploadAvatar(formData);
    open.value = false;
    options.img = res.data.imgUrl;
@@ -164,7 +162,7 @@
}
.user-info-head:hover:after {
  content: "+";
  content: '+';
  position: absolute;
  left: 0;
  right: 0;
src/views/system/user/profile/userInfo.vue
@@ -23,7 +23,7 @@
</template>
<script setup lang="ts">
import { updateUserProfile } from "@/api/system/user";
import { updateUserProfile } from '@/api/system/user';
const props = defineProps({
  user: {
@@ -35,26 +35,31 @@
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userRef = ref<ElFormInstance>();
const rules = ref<ElFormRules>({
  nickName: [{ required: true, message: "用户昵称不能为空", trigger: "blur" }],
  email: [{ required: true, message: "邮箱地址不能为空", trigger: "blur" }, {
    type: "email",
    message: "请输入正确的邮箱地址",
    trigger: ["blur", "change"]
  }],
  phonenumber: [{
    required: true,
    message: "手机号码不能为空",
    trigger: "blur"
  }, { pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }]
  nickName: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }],
  email: [
    { required: true, message: '邮箱地址不能为空', trigger: 'blur' },
    {
      type: 'email',
      message: '请输入正确的邮箱地址',
      trigger: ['blur', 'change']
    }
  ],
  phonenumber: [
    {
      required: true,
      message: '手机号码不能为空',
      trigger: 'blur'
    },
    { pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }
  ]
});
/** æäº¤æŒ‰é’® */
const submit = () => {
  userRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      await updateUserProfile(props.user);
      proxy?.$modal.msgSuccess("修改成功");
      proxy?.$modal.msgSuccess('修改成功');
    }
  });
};
src/views/tool/gen/basicInfoForm.vue
@@ -3,27 +3,27 @@
    <el-row>
      <el-col :span="12">
        <el-form-item label="表名称" prop="tableName">
          <el-input placeholder="请输入仓库名称" v-model="infoForm.tableName" />
          <el-input v-model="infoForm.tableName" placeholder="请输入仓库名称" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="表描述" prop="tableComment">
          <el-input placeholder="请输入" v-model="infoForm.tableComment" />
          <el-input v-model="infoForm.tableComment" placeholder="请输入" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="实体类名称" prop="className">
          <el-input placeholder="请输入" v-model="infoForm.className" />
          <el-input v-model="infoForm.className" placeholder="请输入" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="作者" prop="functionAuthor">
          <el-input placeholder="请输入" v-model="infoForm.functionAuthor" />
          <el-input v-model="infoForm.functionAuthor" placeholder="请输入" />
        </el-form-item>
      </el-col>
      <el-col :span="24">
        <el-form-item label="备注" prop="remark">
          <el-input type="textarea" :rows="3" v-model="infoForm.remark"></el-input>
          <el-input v-model="infoForm.remark" type="textarea" :rows="3"></el-input>
        </el-form-item>
      </el-col>
    </el-row>
@@ -31,19 +31,19 @@
</template>
<script setup lang="ts">
import { propTypes } from "@/utils/propTypes";
import { propTypes } from '@/utils/propTypes';
const prop = defineProps({
  info: propTypes.any.def({})
});
const infoForm = computed(() => prop.info)
const infoForm = computed(() => prop.info);
// è¡¨å•校验
const rules = ref({
  tableName: [{ required: true, message: "请输入表名称", trigger: "blur" }],
  tableComment: [{ required: true, message: "请输入表描述", trigger: "blur" }],
  className: [{ required: true, message: "请输入实体类名称", trigger: "blur" }],
  functionAuthor: [{ required: true, message: "请输入作者", trigger: "blur" }]
  tableName: [{ required: true, message: '请输入表名称', trigger: 'blur' }],
  tableComment: [{ required: true, message: '请输入表描述', trigger: 'blur' }],
  className: [{ required: true, message: '请输入实体类名称', trigger: 'blur' }],
  functionAuthor: [{ required: true, message: '请输入作者', trigger: 'blur' }]
});
</script>
src/views/tool/gen/editTable.vue
@@ -35,22 +35,22 @@
          <el-table-column label="插入" min-width="5%">
            <template #default="scope">
              <el-checkbox true-label="1" false-label="0" v-model="scope.row.isInsert"></el-checkbox>
              <el-checkbox v-model="scope.row.isInsert" true-label="1" false-label="0"></el-checkbox>
            </template>
          </el-table-column>
          <el-table-column label="编辑" min-width="5%">
            <template #default="scope">
              <el-checkbox true-label="1" false-label="0" v-model="scope.row.isEdit"></el-checkbox>
              <el-checkbox v-model="scope.row.isEdit" true-label="1" false-label="0"></el-checkbox>
            </template>
          </el-table-column>
          <el-table-column label="列表" min-width="5%">
            <template #default="scope">
              <el-checkbox true-label="1" false-label="0" v-model="scope.row.isList"></el-checkbox>
              <el-checkbox v-model="scope.row.isList" true-label="1" false-label="0"></el-checkbox>
            </template>
          </el-table-column>
          <el-table-column label="查询" min-width="5%">
            <template #default="scope">
              <el-checkbox true-label="1" false-label="0" v-model="scope.row.isQuery"></el-checkbox>
              <el-checkbox v-model="scope.row.isQuery" true-label="1" false-label="0"></el-checkbox>
            </template>
          </el-table-column>
          <el-table-column label="查询方式" min-width="10%">
@@ -69,7 +69,7 @@
          </el-table-column>
          <el-table-column label="必填" min-width="5%">
            <template #default="scope">
              <el-checkbox true-label="1" false-label="0" v-model="scope.row.isRequired"></el-checkbox>
              <el-checkbox v-model="scope.row.isRequired" true-label="1" false-label="0"></el-checkbox>
            </template>
          </el-table-column>
          <el-table-column label="显示类型" min-width="12%">
@@ -104,7 +104,7 @@
      </el-tab-pane>
    </el-tabs>
    <el-form label-width="100px">
      <div style="text-align: center;margin-left:-100px;margin-top:10px;">
      <div style="text-align: center; margin-left: -100px; margin-top: 10px">
        <el-button type="primary" @click="submitForm()">提交</el-button>
        <el-button @click="close()">返回</el-button>
      </div>
@@ -118,7 +118,7 @@
import { optionselect as getDictOptionselect } from '@/api/system/dict/type';
import { DictTypeVO } from '@/api/system/dict/type/types';
import BasicInfoForm from './basicInfoForm.vue';
import GenInfoForm from "./genInfoForm.vue";
import GenInfoForm from './genInfoForm.vue';
const route = useRoute();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -138,8 +138,8 @@
  const basicForm = basicInfo.value?.$refs.basicInfoForm;
  const genForm = genInfo.value?.$refs.genInfoForm;
  Promise.all([basicForm, genForm].map(getFormPromise)).then(async res => {
    const validateResult = res.every(item => !!item);
  Promise.all([basicForm, genForm].map(getFormPromise)).then(async (res) => {
    const validateResult = res.every((item) => !!item);
    if (validateResult) {
      const genTable: any = Object.assign({}, info.value);
      genTable.columns = columns.value;
@@ -155,24 +155,24 @@
        close();
      }
    } else {
      proxy?.$modal.msgError("表单校验未通过,请重新检查提交内容");
      proxy?.$modal.msgError('表单校验未通过,请重新检查提交内容');
    }
  });
}
};
const getFormPromise = (form: any) => {
  return new Promise(resolve => {
  return new Promise((resolve) => {
    form.validate((res: any) => {
      resolve(res);
    });
  });
}
};
const close = () => {
  const obj = { path: "/tool/gen", query: { t: Date.now(), pageNum: route.query.pageNum } };
  const obj = { path: '/tool/gen', query: { t: Date.now(), pageNum: route.query.pageNum } };
  proxy?.$tab.closeOpenPage(obj);
}
};
(async () => {
  const tableId = route.params && route.params.tableId as string;
  const tableId = route.params && (route.params.tableId as string);
  if (tableId) {
    // èŽ·å–è¡¨è¯¦ç»†ä¿¡æ¯
    const res = await getGenTable(tableId);
src/views/tool/gen/genInfoForm.vue
@@ -95,7 +95,7 @@
        </el-form-item>
      </el-col>
      <el-col :span="24" v-if="infoForm.genType == '1'">
      <el-col v-if="infoForm.genType == '1'" :span="24">
        <el-form-item prop="genPath">
          <template #label>
            è‡ªå®šä¹‰è·¯å¾„
@@ -223,7 +223,7 @@
<script setup lang="ts">
import { listMenu } from '@/api/system/menu';
import { propTypes } from "@/utils/propTypes";
import { propTypes } from '@/utils/propTypes';
interface MenuOptionsType {
  menuId: number | string;
@@ -246,21 +246,21 @@
// è¡¨å•校验
const rules = ref({
  tplCategory: [{ required: true, message: "请选择生成模板", trigger: "blur" }],
  packageName: [{ required: true, message: "请输入生成包路径", trigger: "blur" }],
  moduleName: [{ required: true, message: "请输入生成模块名", trigger: "blur" }],
  businessName: [{ required: true, message: "请输入生成业务名", trigger: "blur" }],
  functionName: [{ required: true, message: "请输入生成功能名", trigger: "blur" }]
  tplCategory: [{ required: true, message: '请选择生成模板', trigger: 'blur' }],
  packageName: [{ required: true, message: '请输入生成包路径', trigger: 'blur' }],
  moduleName: [{ required: true, message: '请输入生成模块名', trigger: 'blur' }],
  businessName: [{ required: true, message: '请输入生成业务名', trigger: 'blur' }],
  functionName: [{ required: true, message: '请输入生成功能名', trigger: 'blur' }]
});
const subSelectChange = () => {
  infoForm.value.subTableFkName = "";
}
  infoForm.value.subTableFkName = '';
};
const tplSelectChange = (value: string) => {
  if (value !== "sub") {
    infoForm.value.subTableName = "";
    infoForm.value.subTableFkName = "";
  if (value !== 'sub') {
    infoForm.value.subTableName = '';
    infoForm.value.subTableFkName = '';
  }
}
};
const setSubTableColumns = (value: string) => {
  table.value.forEach((item: any) => {
    const name = item.tableName;
@@ -268,24 +268,27 @@
      subColumns.value = item.columns;
      return;
    }
  })
}
  });
};
/** æŸ¥è¯¢èœå•下拉树结构 */
const getMenuTreeselect = async () => {
  const res = await listMenu();
  res.data.forEach(m => m.menuId = m.menuId.toString());
  const data = proxy?.handleTree<MenuOptionsType>(res.data, "menuId");
  res.data.forEach((m) => (m.menuId = m.menuId.toString()));
  const data = proxy?.handleTree<MenuOptionsType>(res.data, 'menuId');
  if (data) {
    menuOptions.value = data
    menuOptions.value = data;
  }
}
};
watch(() => props.info.subTableName, val => {
  setSubTableColumns(val);
});
watch(
  () => props.info.subTableName,
  (val) => {
    setSubTableColumns(val);
  }
);
onMounted(() => {
  getMenuTreeselect();
})
});
</script>
src/views/tool/gen/importTable.vue
@@ -1,7 +1,7 @@
<template>
  <!-- å¯¼å…¥è¡¨ -->
  <el-dialog title="导入表" v-model="visible" width="1100px" top="5vh" append-to-body>
    <el-form :model="queryParams" ref="queryFormRef" :inline="true">
  <el-dialog v-model="visible" title="导入表" width="1100px" top="5vh" append-to-body>
    <el-form ref="queryFormRef" :model="queryParams" :inline="true">
      <el-form-item label="数据源" prop="dataName">
        <el-select v-model="queryParams.dataName" filterable placeholder="请选择/输入数据源名称" style="width: 200px">
          <el-option v-for="item in dataNameList" :key="item" :label="item" :value="item"> </el-option>
@@ -19,14 +19,14 @@
      </el-form-item>
    </el-form>
    <el-row>
      <el-table @row-click="clickRow" ref="tableRef" :data="dbTableList" @selection-change="handleSelectionChange" height="260px">
      <el-table ref="tableRef" :data="dbTableList" height="260px" @row-click="clickRow" @selection-change="handleSelectionChange">
        <el-table-column type="selection" width="55"></el-table-column>
        <el-table-column prop="tableName" label="表名称" :show-overflow-tooltip="true"></el-table-column>
        <el-table-column prop="tableComment" label="表描述" :show-overflow-tooltip="true"></el-table-column>
        <el-table-column prop="createTime" label="创建时间"></el-table-column>
        <el-table-column prop="updateTime" label="更新时间"></el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
    </el-row>
    <template #footer>
      <div class="dialog-footer">
@@ -59,7 +59,7 @@
});
const dataNameList = ref<Array<string>>([]);
const emit = defineEmits(["ok"]);
const emit = defineEmits(['ok']);
/** æŸ¥è¯¢å‚数列表 */
const show = (dataName: string) => {
@@ -71,53 +71,53 @@
  }
  getList();
  visible.value = true;
}
};
/** å•击选择行 */
const clickRow = (row: DbTableVO) => {
  // ele bug
  tableRef.value?.toggleRowSelection(row, false);
}
};
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: DbTableVO[]) => {
  tables.value = selection.map(item => item.tableName);
}
  tables.value = selection.map((item) => item.tableName);
};
/** æŸ¥è¯¢è¡¨æ•°æ® */
const getList = async () => {
  const res = await listDbTable(queryParams);
  dbTableList.value = res.rows;
  total.value = res.total;
}
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.pageNum = 1;
  getList();
}
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value?.resetFields();
  handleQuery();
}
};
/** å¯¼å…¥æŒ‰é’®æ“ä½œ */
const handleImportTable = async () => {
  const tableNames = tables.value.join(",");
  if (tableNames == "") {
    proxy?.$modal.msgError("请选择要导入的表");
  const tableNames = tables.value.join(',');
  if (tableNames == '') {
    proxy?.$modal.msgError('请选择要导入的表');
    return;
  }
  const res = await importTable({ tables: tableNames, dataName: queryParams.dataName });
  proxy?.$modal.msgSuccess(res.msg);
  if (res.code === 200) {
    visible.value = false;
    emit("ok");
    emit('ok');
  }
}
};
/** æŸ¥è¯¢å¤šæ•°æ®æºåç§° */
const getDataNameList = async () => {
  const res = await getDataNames()
  const res = await getDataNames();
  dataNameList.value = res.data;
}
};
defineExpose({
  show,
  show
});
</script>
src/views/tool/gen/index.vue
@@ -1,9 +1,9 @@
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
      <div class="mb-[10px]" v-show="showSearch">
      <div v-show="showSearch" class="mb-[10px]">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
            <el-form-item label="数据源" prop="dataName">
              <el-select v-model="queryParams.dataName" filterable clearable placeholder="请选择/输入数据源名称" style="width: 200px">
                <el-option key="" label="全部" value="" />
@@ -39,20 +39,20 @@
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
            <el-button type="primary" plain icon="Download" @click="handleGenTable()" v-hasPermi="['tool:gen:code']">生成</el-button>
            <el-button v-hasPermi="['tool:gen:code']" type="primary" plain icon="Download" @click="handleGenTable()">生成</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="info" plain icon="Upload" @click="openImportTable" v-hasPermi="['tool:gen:import']">导入</el-button>
            <el-button v-hasPermi="['tool:gen:import']" type="info" plain icon="Upload" @click="openImportTable">导入</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleEditTable()" v-hasPermi="['tool:gen:edit']">修改</el-button>
            <el-button v-hasPermi="['tool:gen:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleEditTable()">修改</el-button>
          </el-col>
          <el-col :span="1.5">
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['tool:gen:remove']">
            <el-button v-hasPermi="['tool:gen:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
              åˆ é™¤
            </el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
          <right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar>
        </el-row>
      </template>
@@ -72,28 +72,28 @@
        <el-table-column label="操作" align="center" width="330" class-name="small-padding fixed-width">
          <template #default="scope">
            <el-tooltip content="预览" placement="top">
              <el-button link type="primary" icon="View" @click="handlePreview(scope.row)" v-hasPermi="['tool:gen:preview']"></el-button>
              <el-button v-hasPermi="['tool:gen:preview']" link type="primary" icon="View" @click="handlePreview(scope.row)"></el-button>
            </el-tooltip>
            <el-tooltip content="编辑" placement="top">
              <el-button link type="primary" icon="Edit" @click="handleEditTable(scope.row)" v-hasPermi="['tool:gen:edit']"></el-button>
              <el-button v-hasPermi="['tool:gen:edit']" link type="primary" icon="Edit" @click="handleEditTable(scope.row)"></el-button>
            </el-tooltip>
            <el-tooltip content="删除" placement="top">
              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['tool:gen:remove']"></el-button>
              <el-button v-hasPermi="['tool:gen:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
            </el-tooltip>
            <el-tooltip content="同步" placement="top">
              <el-button link type="primary" icon="Refresh" @click="handleSynchDb(scope.row)" v-hasPermi="['tool:gen:edit']"></el-button>
              <el-button v-hasPermi="['tool:gen:edit']" link type="primary" icon="Refresh" @click="handleSynchDb(scope.row)"></el-button>
            </el-tooltip>
            <el-tooltip content="生成代码" placement="top">
              <el-button link type="primary" icon="Download" @click="handleGenTable(scope.row)" v-hasPermi="['tool:gen:code']"></el-button>
              <el-button v-hasPermi="['tool:gen:code']" link type="primary" icon="Download" @click="handleGenTable(scope.row)"></el-button>
            </el-tooltip>
          </template>
        </el-table-column>
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
      <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
    </el-card>
    <!-- é¢„览界面 -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="80%" top="5vh" append-to-body class="scrollbar">
    <el-dialog v-model="dialog.visible" :title="dialog.title" width="80%" top="5vh" append-to-body class="scrollbar">
      <el-tabs v-model="preview.activeName">
        <el-tab-pane
          v-for="(value, key) in preview.data"
@@ -101,7 +101,7 @@
          :name="key.substring(key.lastIndexOf('/') + 1, key.indexOf('.vm'))"
          :key="value"
        >
          <el-link :underline="false" icon="DocumentCopy" v-copyText="value" v-copyText:callback="copyTextSuccess" style="float:right">
          <el-link v-copyText="value" v-copyText:callback="copyTextSuccess" :underline="false" icon="DocumentCopy" style="float: right">
            &nbsp;复制
          </el-link>
          <pre>{{ value }}</pre>
@@ -129,7 +129,7 @@
const multiple = ref(true);
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const uniqueId = ref("");
const uniqueId = ref('');
const dataNameList = ref<Array<string>>([]);
const queryFormRef = ref<ElFormInstance>();
@@ -140,13 +140,13 @@
  pageSize: 10,
  tableName: '',
  tableComment: '',
  dataName: ""
})
  dataName: ''
});
const preview = ref<any>({
  data: {},
  activeName: 'domain.java'
})
});
const dialog = reactive<DialogOption>({
  visible: false,
  title: '代码预览'
@@ -161,13 +161,13 @@
    queryFormRef.value?.resetFields();
    getList();
  }
})
});
/** æŸ¥è¯¢å¤šæ•°æ®æºåç§° */
const getDataNameList = async () => {
  const res = await getDataNames()
  const res = await getDataNames();
  dataNameList.value = res.data;
}
};
/** æŸ¥è¯¢è¡¨é›†åˆ */
const getList = async () => {
@@ -176,65 +176,65 @@
  tableList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
};
/** ç”Ÿæˆä»£ç æ“ä½œ */
const handleGenTable = async (row?: TableVO) => {
  const tbIds = row?.tableId || ids.value;
  if (tbIds == "") {
  if (tbIds == '') {
    proxy?.$modal.msgError('请选择要生成的数据');
    return;
  }
  if (row?.genType === "1") {
  if (row?.genType === '1') {
    await genCode(row.tableId);
    proxy?.$modal.msgSuccess('成功生成到自定义路径:' + row.genPath);
  } else {
    proxy?.$download.zip('/tool/gen/batchGenCode?tableIdStr=' + tbIds, 'ruoyi.zip');
  }
}
};
/** åŒæ­¥æ•°æ®åº“操作 */
const handleSynchDb = async (row: TableVO) => {
  const tableId = row.tableId;
  await proxy?.$modal.confirm('确认要强制同步"' + row.tableName + '"表结构吗?');
  await synchDb(tableId);
  proxy?.$modal.msgSuccess('同步成功');
}
};
/** æ‰“开导入表弹窗 */
const openImportTable = () => {
  importRef.value?.show(queryParams.value.dataName);
}
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  dateRange.value = ['', ''];
  queryFormRef.value?.resetFields();
  handleQuery();
}
};
/** é¢„览按钮 */
const handlePreview = async (row: TableVO) => {
  const res = await previewTable(row.tableId);
  preview.value.data = res.data;
  dialog.visible = true;
  preview.value.activeName = 'domain.java';
}
};
/** å¤åˆ¶ä»£ç æˆåŠŸ */
const copyTextSuccess = () => {
  proxy?.$modal.msgSuccess('复制成功');
}
};
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
const handleSelectionChange = (selection: TableVO[]) => {
  ids.value = selection.map(item => item.tableId);
  ids.value = selection.map((item) => item.tableId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
};
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleEditTable = (row?: TableVO) => {
  const tableId = row?.tableId || ids.value[0];
  router.push({ path: '/tool/gen-edit/index/' + tableId, query: { pageNum: queryParams.value.pageNum } });
}
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: TableVO) => {
  const tableIds = row?.tableId || ids.value;
@@ -242,10 +242,10 @@
  await delTable(tableIds);
  await getList();
  proxy?.$modal.msgSuccess('删除成功');
}
};
onMounted(() => {
  getList();
  getDataNameList();
})
});
</script>
tsconfig.json
@@ -1,8 +1,8 @@
{
  "compilerOptions": {
    "target": "es2022",
    "useDefineForClassFields": true,
    "target": "esnext",
    "module": "esnext",
    "useDefineForClassFields": true,
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
@@ -21,6 +21,6 @@
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*.ts", "src/**/*.vue", "src/types/**/*.d.ts"],
  "exclude": ["node_modules", "dist", "**/*.js"]
  "include": ["src/**/*.ts", "src/**/*.vue", "src/types/**/*.d.ts", "vite.config.ts"],
  "exclude": ["node_modules", "dist", "**/*.js", "**/*.md", "src/**/*.md"]
}