兰宝车间质量管理系统-前端
疯狂的狮子Li
2023-07-11 c13c622eac5551c6f099f148feb5256711ca34de
!25 部分优化以及新增功能
Merge pull request !25 from ahaos/tspr
已修改68个文件
已添加5个文件
6455 ■■■■ 文件已修改
.eslintrc-auto-import.json 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.eslintrc.js 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/user/index.ts 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/index.scss 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/ruoyi.scss 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/sidebar.scss 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/variables.module.scss 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Breadcrumb/index.vue 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/BuildCode/index.vue 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/BuildCode/render.vue 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/DictTag/index.vue 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Editor/index.vue 202 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/FileUpload/index.vue 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Hamburger/index.vue 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/HeaderSearch/index.vue 80 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/IconSelect/index.vue 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ImagePreview/index.vue 58 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ImageUpload/index.vue 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Pagination/index.vue 54 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/RightToolbar/index.vue 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/SvgIcon/index.vue 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/TreeSelect/index.vue 76 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/iFrame/index.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/IframeToggle/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Navbar.vue 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Settings/index.vue 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/TagsView/ScrollPane.vue 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/TagsView/index.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/topBar/search.vue 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/modal.ts 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/tab.ts 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/tagsView.ts 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/element.d.ts 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/global.d.ts 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/module.d.ts 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/router.d.ts 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/vform3-builds.d.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/propTypes.ts 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/demo/demo/index.vue 80 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/demo/tree/index.vue 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/error/401.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login.vue 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/cache/index.vue 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/logininfor/index.vue 76 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/online/index.vue 71 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/operlog/index.vue 214 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/register.vue 125 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/config/index.vue 208 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dept/index.vue 223 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dict/data.vue 228 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/dict/index.vue 212 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/menu/index.vue 205 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/notice/index.vue 178 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/oss/config.vue 330 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/oss/index.vue 296 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/post/index.vue 179 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/authUser.vue 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/index.vue 430 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/role/selectUser.vue 69 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/tenant/index.vue 284 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/tenantPackage/index.vue 347 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/authRole.vue 78 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/index.vue 425 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/resetPwd.vue 56 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/userAvatar.vue 120 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/userInfo.vue 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/basicInfoForm.vue 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/editTable.vue 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/genInfoForm.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/importTable.vue 87 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/index.vue 198 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.eslintrc-auto-import.json
@@ -1,5 +1,36 @@
{
  "globals": {
    "ComponentInternalInstance": true,
    "TransferKey": true,
    "ElFormRules": true,
    "CheckboxValueType": true,
    "PropType": true,
    "DateModelType": true,
    "UploadFile": true,
    "ElFormInstance": true,
    "ElTableInstance": true,
    "ElTreeInstance": true,
    "ElTreeSelectInstance": true,
    "ElSelectInstance": true,
    "ElUploadInstance": true,
    "ElCardInstance": true,
    "ElDialogInstance": true,
    "ElInputInstance": true,
    "ElInputNumberInstance": true,
    "ElRadioInstance": true,
    "ElRadioGroupInstance": true,
    "ElRadioButtonInstance": true,
    "ElCheckboxInstance": true,
    "ElCheckboxGroupInstance": true,
    "ElSwitchInstance": true,
    "ElDatePickerInstance": true,
    "ElTimePickerInstance": true,
    "ElTimeSelectInstance": true,
    "ElScrollbarInstance": true,
    "ElCascaderInstance": true,
    "ElColorPickerInstance": true,
    "ElRateInstance": true,
    "ElSliderInstance": true,
    "useRouter": true,
    "useRoute": true,
    "EffectScope": true,
.eslintrc.js
@@ -29,7 +29,8 @@
        // å…³é—­ç©ºç±»åž‹æ£€æŸ¥ {}
        extendDefaults: true,
        types: {
          '{}': false
          '{}': false,
          'Function': false
        }
      }
    ]
package.json
@@ -35,10 +35,12 @@
    "path-to-regexp": "6.2.0",
    "pinia": "2.0.22",
    "screenfull": "6.0.0",
    "vform3-builds": "3.0.8",
    "vue": "3.2.45",
    "vue-cropper": "1.0.3",
    "vue-i18n": "9.2.2",
    "vue-router": "4.1.4"
    "vue-router": "4.1.4",
    "vue-types": "^5.0.3"
  },
  "devDependencies": {
    "@iconify/json": "^2.2.40",
@@ -71,11 +73,11 @@
    "unplugin-auto-import": "0.13.0",
    "unplugin-icons": "0.15.1",
    "unplugin-vue-components": "0.23.0",
    "vite": "4.3.1",
    "vite-plugin-compression": "0.5.1",
    "vite-plugin-svg-icons": "2.0.1",
    "vite-plugin-vue-setup-extend": "^0.4.0",
    "vitest": "^0.29.7",
    "vite": "4.3.1",
    "vue-eslint-parser": "9.1.0",
    "vue-tsc": "0.35.0"
  }
src/api/system/user/index.ts
@@ -9,64 +9,64 @@
 * æŸ¥è¯¢ç”¨æˆ·åˆ—表
 * @param query
 */
export function listUser(query: UserQuery): AxiosPromise<UserVO[]> {
export const listUser = (query: UserQuery): AxiosPromise<UserVO[]> => {
  return request({
    url: '/system/user/list',
    method: 'get',
    params: query
  });
}
};
/**
 * èŽ·å–ç”¨æˆ·è¯¦æƒ…
 * @param userId
 */
export function getUser(userId?: string | number): AxiosPromise<UserInfoVO> {
export const getUser = (userId?: string | number): AxiosPromise<UserInfoVO> => {
  return request({
    url: '/system/user/' + parseStrEmpty(userId),
    method: 'get'
  });
}
};
/**
 * æ–°å¢žç”¨æˆ·
 */
export function addUser(data: UserForm) {
export const addUser = (data: UserForm) => {
  return request({
    url: '/system/user',
    method: 'post',
    data: data
  });
}
};
/**
 * ä¿®æ”¹ç”¨æˆ·
 */
export function updateUser(data: UserForm) {
export const updateUser = (data: UserForm) => {
  return request({
    url: '/system/user',
    method: 'put',
    data: data
  });
}
};
/**
 * åˆ é™¤ç”¨æˆ·
 * @param userId ç”¨æˆ·ID
 */
export function delUser(userId: Array<string | number> | string | number) {
export const delUser = (userId: Array<string | number> | string | number) => {
  return request({
    url: '/system/user/' + userId,
    method: 'delete'
  });
}
};
/**
 * ç”¨æˆ·å¯†ç é‡ç½®
 * @param userId ç”¨æˆ·ID
 * @param password å¯†ç 
 */
export function resetUserPwd(userId: string | number, password: string) {
export const resetUserPwd = (userId: string | number, password: string) => {
  const data = {
    userId,
    password
@@ -76,14 +76,14 @@
    method: 'put',
    data: data
  });
}
};
/**
 * ç”¨æˆ·çŠ¶æ€ä¿®æ”¹
 * @param userId ç”¨æˆ·ID
 * @param status ç”¨æˆ·çŠ¶æ€
 */
export function changeUserStatus(userId: number | string, status: string) {
export const changeUserStatus = (userId: number | string, status: string) => {
  const data = {
    userId,
    status
@@ -93,36 +93,36 @@
    method: 'put',
    data: data
  });
}
};
/**
 * æŸ¥è¯¢ç”¨æˆ·ä¸ªäººä¿¡æ¯
 */
export function getUserProfile(): AxiosPromise<UserInfoVO> {
export const getUserProfile = (): AxiosPromise<UserInfoVO> => {
  return request({
    url: '/system/user/profile',
    method: 'get'
  });
}
};
/**
 * ä¿®æ”¹ç”¨æˆ·ä¸ªäººä¿¡æ¯
 * @param data ç”¨æˆ·ä¿¡æ¯
 */
export function updateUserProfile(data: UserForm) {
export const updateUserProfile = (data: UserForm) => {
  return request({
    url: '/system/user/profile',
    method: 'put',
    data: data
  });
}
};
/**
 * ç”¨æˆ·å¯†ç é‡ç½®
 * @param oldPassword æ—§å¯†ç 
 * @param newPassword æ–°å¯†ç 
 */
export function updateUserPwd(oldPassword: string, newPassword: string) {
export const updateUserPwd = (oldPassword: string, newPassword: string) => {
  const data = {
    oldPassword,
    newPassword
@@ -132,49 +132,66 @@
    method: 'put',
    params: data
  });
}
};
/**
 * ç”¨æˆ·å¤´åƒä¸Šä¼ 
 * @param data å¤´åƒæ–‡ä»¶
 */
export function uploadAvatar(data: FormData) {
export const uploadAvatar = (data: FormData) => {
  return request({
    url: '/system/user/profile/avatar',
    method: 'post',
    data: data
  });
}
};
/**
 * æŸ¥è¯¢æŽˆæƒè§’色
 * @param userId ç”¨æˆ·ID
 */
export function getAuthRole(userId: string | number): AxiosPromise<{ user: UserVO; roles: RoleVO[] }> {
export const getAuthRole = (userId: string | number): AxiosPromise<{ user: UserVO; roles: RoleVO[] }> => {
  return request({
    url: '/system/user/authRole/' + userId,
    method: 'get'
  });
}
};
/**
 * ä¿å­˜æŽˆæƒè§’色
 * @param data ç”¨æˆ·ID
 */
export function updateAuthRole(data: { userId: string; roleIds: string }) {
export const updateAuthRole = (data: { userId: string; roleIds: string }) => {
  return request({
    url: '/system/user/authRole',
    method: 'put',
    params: data
  });
}
};
/**
 * æŸ¥è¯¢éƒ¨é—¨ä¸‹æ‹‰æ ‘结构
 */
export function deptTreeSelect(): AxiosPromise<DeptVO[]> {
export const deptTreeSelect = (): AxiosPromise<DeptVO[]> => {
  return request({
    url: '/system/user/deptTree',
    method: 'get'
  });
}
};
export default {
  listUser,
  getUser,
  addUser,
  updateUser,
  delUser,
  resetUserPwd,
  changeUserStatus,
  getUserProfile,
  updateUserProfile,
  updateUserPwd,
  uploadAvatar,
  getAuthRole,
  updateAuthRole,
  deptTreeSelect
};
src/assets/styles/index.scss
@@ -26,6 +26,10 @@
  box-sizing: border-box;
}
html.dark .svg-icon, html.dark svg {
  fill: var(--el-text-color-regular);
}
#app {
  height: 100%;
}
@@ -137,6 +141,7 @@
  border: 1px solid var(--el-border-color-light);
  background-color: var(--el-bg-color-overlay);
  padding: 0.75rem;
  transition: all ease 0.3s;
  &:hover {
    box-shadow: 0 2px 12px #0000001a;
@@ -199,4 +204,4 @@
    vertical-align: middle;
    margin-bottom: 10px;
  }
}
}
src/assets/styles/ruoyi.scss
@@ -90,8 +90,8 @@
  .el-table__fixed-header-wrapper {
    th {
      word-break: break-word;
      background-color: #f8f8f9 !important;
      color: #515a6e;
      background-color: $table-header-bg !important;
      color: $table-header-text-color;
      height: 40px !important;
      font-size: 13px;
    }
src/assets/styles/sidebar.scss
@@ -84,7 +84,7 @@
    .sub-menu-title-noDropdown,
    .el-sub-menu__title {
      &:hover {
        background-color: rgba(0, 0, 0, 0.06) !important;
        background-color: $base-sub-menu-title-hover !important;
      }
    }
@@ -211,7 +211,7 @@
  .el-menu-item {
    &:hover {
      // you can use $sub-menuHover
      background-color: rgba(0, 0, 0, 0.06) !important;
      background-color: $base-menu-hover !important;
    }
  }
src/assets/styles/variables.module.scss
@@ -1,3 +1,38 @@
// å…¨å±€SCSS变量
:root {
  --menuBg: #304156;
  --menuColor: #bfcbd9;
  --menuActiveText: #f4f4f5;
  --menuHover: #263445;
  --subMenuBg: #1f2d3d;
  --subMenuActiveText: #f4f4f5;
  --subMenuHover: #001528;
  --subMenuTitleHover: #293444;
  --tableHeaderBg: #f8f8f9;
  --tableHeaderTextColor: #515a6e;
}
html.dark {
  --menuBg: #1d1e1f;
  --menuColor: #bfcbd9;
  --menuActiveText: #f4f4f5;
  --menuHover: #171819;
  --subMenuBg: #1d1e1f;
  --subMenuActiveText: #1d1e1f;
  --subMenuHover: #171819;
  --subMenuTitleHover: #171819;
  --tableHeaderBg: var(--el-bg-color);
  --tableHeaderTextColor: var(--el-text-color);
  // è¦†ç›–ele é«˜äº®å½“前行的标准暗色
  .el-tree-node__content {
    --el-color-primary-light-9: #262727;
  }
}
// base color
$blue: #324157;
$light-blue: #3a71a8;
@@ -9,32 +44,22 @@
$panGreen: #30b08f;
// é»˜è®¤èœå•主题风格
$base-menu-color: #bfcbd9;
$base-menu-color-active: #f4f4f5;
$base-menu-background: #304156;
$base-menu-color: var(--menuColor);
$base-menu-hover: var(--menuHover);
$base-menu-color-active: var(--menuActiveText);
$base-menu-background: var(--menuBg);
$base-logo-title-color: #ffffff;
$base-menu-light-color: rgba(0, 0, 0, 0.7);
$base-menu-light-background: #ffffff;
$base-logo-light-title-color: #001529;
$base-sub-menu-background: #1f2d3d;
$base-sub-menu-hover: #001528;
// è‡ªå®šä¹‰æš—色菜单风格
/**
$base-menu-color:hsla(0,0%,100%,.65);
$base-menu-color-active:#fff;
$base-menu-background:#001529;
$base-logo-title-color: #ffffff;
$base-menu-light-color:rgba(0,0,0,.70);
$base-menu-light-background:#ffffff;
$base-logo-light-title-color: #001529;
$base-sub-menu-background:#000c17;
$base-sub-menu-hover:#001528;
*/
$base-sub-menu-background: var(--subMenuBg);
$base-sub-menu-hover: var(--subMenuHover);
$base-sub-menu-title-hover: var(--subMenuTitleHover);
// è¡¨å•头背景色和标题颜色
$table-header-bg: var(--tableHeaderBg);
$table-header-text-color: var(--tableHeaderTextColor);
$--color-primary: #409eff;
$--color-success: #67c23a;
src/components/Breadcrumb/index.vue
@@ -18,34 +18,34 @@
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]
    // åˆ¤æ–­æ˜¯å¦ä¸ºé¦–页
    if (!isDashboard(first)) {
        matched = ([{ path: '/index', meta: { title: '首页' } }] as any).concat(matched)
    }
    levelList.value = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
  // only show routes with meta.title
  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)
  }
  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
    if (!name) {
        return false
    }
    return name.trim() === 'Index'
  const name = route && route.name as string
  if (!name) {
    return false
  }
  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 you go to the redirect page, do not update the breadcrumbs
  if (route.path.startsWith('/redirect/')) return
  getBreadcrumb()
})
onMounted(() => {
    getBreadcrumb();
  getBreadcrumb();
})
</script>
src/components/BuildCode/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,64 @@
<!-- ä»£ç æž„建 -->
<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"
      :designer-config="{ importJsonButton: true, exportJsonButton: true, exportCodeButton: true, generateSFCButton: true, formTemplates: true }"
    >
      <template #customToolButtons v-if="showBtn">
        <el-button link type="primary" icon="Select" @click="getJson">保存</el-button>
      </template>
    </v-form-designer>
  </div>
</template>
<style lang="scss">
.build {
  margin: 0 !important;
  overflow-y: auto !important;
  & header.main-header {
    display: none;
  }
  & .right-toolbar-con {
    text-align: right !important;
  }
}
</style>
src/components/BuildCode/render.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,62 @@
<!-- åŠ¨æ€è¡¨å•æ¸²æŸ“ -->
<script setup name="Render">
const props = defineProps({
  formJson: {
    type: [String, Object],
    default: {}
  },
  formData: {
    type: [String, Object],
    default: {}
  },
  isView: {
    type: Boolean,
    default: false
  }
})
const vFormRef = ref(null)
// èŽ·å–è¡¨å•æ•°æ®-异步
const getFormData = () => {
  return vFormRef.value.getFormData()
}
/**
 * è®¾ç½®è¡¨å•内容
 * @param {表单配置} formConf
 * formConfig:{ formTemplate:表单模板,formData:表单数据,hiddenField:需要隐藏的字段字符串集合,disabledField:需要禁用的自读字符串集合}
 */
const initForm = (formConf) => {
  const { formTemplate, formData, hiddenField, disabledField } = toRaw(formConf)
  if (formTemplate) {
    vFormRef.value.setFormJson(formTemplate)
    if (formData) {
      vFormRef.value.setFormData(formData)
    }
    if (disabledField && disabledField.length > 0) {
      setTimeout(() => {
        vFormRef.value.disableWidgets(disabledField)
      }, 200)
    }
    if (hiddenField && hiddenField.length > 0) {
      setTimeout(() => {
        vFormRef.value.hideWidgets(hiddenField)
      }, 200)
    }
    if (props.isView) {
      console.log(props.isView)
      setTimeout(() => {
        vFormRef.value.disableForm()
      }, 100)
    }
  }
}
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
@@ -24,7 +24,7 @@
</template>
<script setup lang="ts">
import { PropType } from 'vue';
import { propTypes } from '@/utils/propTypes';
const props = defineProps({
@@ -36,10 +36,7 @@
  // å½“前的值
  value: [Number, String, Array] as PropType<number | string | Array<number | string>>,
  // å½“未找到匹配的数据时,显示value
  showValue: {
    type: Boolean as PropType<boolean>,
    default: true,
  },
  showValue: propTypes.bool.def(true),
});
const values = computed(() => {
src/components/Editor/index.vue
@@ -30,152 +30,139 @@
import { QuillEditor, Quill } from '@vueup/vue-quill';
import '@vueup/vue-quill/dist/vue-quill.snow.css';
import { getToken } from "@/utils/auth";
import { ComponentInternalInstance } from "vue";
import { propTypes } from '@/utils/propTypes';
const props = defineProps({
    /* ç¼–辑器的内容 */
    modelValue: {
        type: String,
    },
    /* é«˜åº¦ */
    height: {
        type: Number,
        default: null,
    },
    /* æœ€å°é«˜åº¦ */
    minHeight: {
        type: Number,
        default: null,
    },
    /* åªè¯» */
    readOnly: {
        type: Boolean,
        default: false,
    },
    /* ä¸Šä¼ æ–‡ä»¶å¤§å°é™åˆ¶(MB) */
    fileSize: {
        type: Number,
        default: 5,
    },
    /* ç±»åž‹ï¼ˆbase64格式、url格式) */
    type: {
        type: String,
        default: "url",
    }
  /* ç¼–辑器的内容 */
  modelValue: propTypes.string,
  /* é«˜åº¦ */
  height: propTypes.number.def(400),
  /* æœ€å°é«˜åº¦ */
  minHeight: propTypes.number.def(400),
  /* åªè¯» */
  readOnly: propTypes.bool.def(false),
  /* ä¸Šä¼ æ–‡ä»¶å¤§å°é™åˆ¶(MB) */
  fileSize: propTypes.number.def(5),
  /* ç±»åž‹ï¼ˆbase64格式、url格式) */
  type: propTypes.string.def('url')
});
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const upload = reactive<UploadOption>({
    headers: { Authorization: "Bearer " + getToken() },
    url: import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload'
  headers: { Authorization: "Bearer " + getToken() },
  url: import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload'
})
const myQuillEditor = ref();
const options = ref({
    theme: "snow",
    bounds: document.body,
    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"]                       // é“¾æŽ¥ã€å›¾ç‰‡ã€è§†é¢‘
            ],
            handlers: {
                image: function (value: any) {
                    if (value) {
                        // è°ƒç”¨element图片上传
                        (document.querySelector(".editor-img-uploader>.el-upload") as HTMLDivElement)?.click();
                    } else {
                        Quill.format("image", true);
                    }
                },
            },
        }
    },
    placeholder: '请输入内容',
    readOnly: props.readOnly,
  theme: "snow",
  bounds: document.body,
  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"]                       // é“¾æŽ¥ã€å›¾ç‰‡ã€è§†é¢‘
      ],
      handlers: {
        image: function (value: any) {
          if (value) {
            // è°ƒç”¨element图片上传
            (document.querySelector(".editor-img-uploader>.el-upload") as HTMLDivElement)?.click();
          } else {
            Quill.format("image", true);
          }
        },
      },
    }
  },
  placeholder: '请输入内容',
  readOnly: props.readOnly,
});
const styles = computed(() => {
    let style: any = {};
    if (props.minHeight) {
        style.minHeight = `${props.minHeight}px`;
    }
    if (props.height) {
        style.height = `${props.height}px`;
    }
    return style;
  let style: any = {};
  if (props.minHeight) {
    style.minHeight = `${props.minHeight}px`;
  }
  if (props.height) {
    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;
    }
  if (v !== content.value) {
    content.value = v === undefined ? "<p></p>" : v;
  }
}, { immediate: true });
// å›¾ç‰‡ä¸Šä¼ æˆåŠŸè¿”å›žå›¾ç‰‡åœ°å€
const handleUploadSuccess = (res: any) => {
    // èŽ·å–å¯Œæ–‡æœ¬å®žä¾‹
    let quill = toRaw(myQuillEditor.value).getQuill();
    // å¦‚果上传成功
    if (res.code === 200) {
        // èŽ·å–å…‰æ ‡ä½ç½®
        let length = quill.selection.savedRange.index;
        // æ’入图片,res为服务器返回的图片链接地址
        quill.insertEmbed(length, "image", res.data.url);
        // è°ƒæ•´å…‰æ ‡åˆ°æœ€åŽ
        quill.setSelection(length + 1);
        proxy?.$modal.closeLoading();
    } else {
        proxy?.$modal.loading(res.msg);
        proxy?.$modal.closeLoading();
    }
  // èŽ·å–å¯Œæ–‡æœ¬å®žä¾‹
  let quill = toRaw(myQuillEditor.value).getQuill();
  // å¦‚果上传成功
  if (res.code === 200) {
    // èŽ·å–å…‰æ ‡ä½ç½®
    let length = quill.selection.savedRange.index;
    // æ’入图片,res为服务器返回的图片链接地址
    quill.insertEmbed(length, "image", res.data.url);
    // è°ƒæ•´å…‰æ ‡åˆ°æœ€åŽ
    quill.setSelection(length + 1);
    proxy?.$modal.closeLoading();
  } else {
    proxy?.$modal.loading(res.msg);
    proxy?.$modal.closeLoading();
  }
}
// å›¾ç‰‡ä¸Šä¼ å‰æ‹¦æˆª
const handleBeforeUpload = (file: any) => {
    // æ ¡æ£€æ–‡ä»¶å¤§å°
    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('正在上传文件,请稍候...');
    return true;
  }
  proxy?.$modal.loading('正在上传文件,请稍候...');
  return true;
}
// å›¾ç‰‡å¤±è´¥æ‹¦æˆª
const handleUploadError = (err: any) => {
    console.error(err);
    proxy?.$modal.msgError('上传文件失败');
  console.error(err);
  proxy?.$modal.msgError('上传文件失败');
}
</script>
<style>
.editor, .ql-toolbar {
.editor,
.ql-toolbar {
  white-space: pre-wrap !important;
  line-height: normal !important;
}
.quill-img {
  display: none;
}
.ql-snow .ql-tooltip[data-mode="link"]::before {
  content: "请输入链接地址:";
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
  border-right: 0;
  content: "保存";
@@ -190,14 +177,17 @@
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
  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="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";
@@ -207,26 +197,32 @@
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
  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="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="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="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
  content: "标题6";
@@ -236,10 +232,12 @@
.ql-snow .ql-picker.ql-font .ql-picker-item::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: "等宽字体";
src/components/FileUpload/index.vue
@@ -45,31 +45,18 @@
<script setup lang="ts">
import { getToken } from "@/utils/auth";
import { listByIds, delOss } from "@/api/system/oss";
import { ComponentInternalInstance } from "vue";
import { ElUpload, UploadFile } from "element-plus";
import { propTypes } from '@/utils/propTypes';
const props = defineProps({
    modelValue: [String, Object, Array],
    // æ•°é‡é™åˆ¶
    limit: {
        type: Number,
        default: 5,
    },
    limit: propTypes.number.def(5),
    // å¤§å°é™åˆ¶(MB)
    fileSize: {
        type: Number,
        default: 5,
    },
    fileSize: propTypes.number.def(5),
    // æ–‡ä»¶ç±»åž‹, ä¾‹å¦‚['png', 'jpg', 'jpeg']
    fileType: {
        type: Array,
        default: () => ["doc", "xls", "ppt", "txt", "pdf"],
    },
    fileType: propTypes.array.def(["doc", "xls", "ppt", "txt", "pdf"]),
    // æ˜¯å¦æ˜¾ç¤ºæç¤º
    isShowTip: {
        type: Boolean,
        default: true
    }
    isShowTip: propTypes.bool.def(true),
});
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -86,7 +73,7 @@
    () => props.isShowTip && (props.fileType || props.fileSize)
);
const fileUploadRef = ref(ElUpload);
const fileUploadRef = ref<ElUploadInstance>();
watch(() => props.modelValue, async val => {
    if (val) {
@@ -96,7 +83,7 @@
        if (Array.isArray(val)) {
            list = val;
        } else {
            const res =  await listByIds(val as string)
            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;
@@ -104,7 +91,7 @@
        }
        // ç„¶åŽå°†æ•°ç»„转为对象数组
        fileList.value = list.map(item => {
            item = {name: item.name, url: item.url, ossId: item.ossId};
            item = { name: item.name, url: item.url, ossId: item.ossId };
            item.uid = item.uid || new Date().getTime() + temp++;
            return item;
        });
@@ -112,7 +99,7 @@
        fileList.value = [];
        return [];
    }
},{ deep: true, immediate: true });
}, { deep: true, immediate: true });
// ä¸Šä¼ å‰æ ¡æ£€æ ¼å¼å’Œå¤§å°
const handleBeforeUpload = (file: any) => {
@@ -150,7 +137,7 @@
}
// ä¸Šä¼ æˆåŠŸå›žè°ƒ
const handleUploadSuccess = (res:any, file: UploadFile) => {
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();
@@ -158,7 +145,7 @@
        number.value--;
        proxy?.$modal.closeLoading();
        proxy?.$modal.msgError(res.msg);
        fileUploadRef.value.handleRemove(file);
        fileUploadRef.value?.handleRemove(file);
        uploadedSuccessfully();
    }
}
@@ -172,7 +159,7 @@
}
// ä¸Šä¼ ç»“束处理
const uploadedSuccessfully =() => {
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 = [];
@@ -207,21 +194,24 @@
<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,6 +1,6 @@
<template>
  <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">
    <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"
      />
@@ -9,16 +9,15 @@
</template>
<script setup lang="ts">
import { propTypes } from '@/utils/propTypes';
defineProps({
    isActive: {
        type: Boolean,
        default: false
    }
  isActive: propTypes.bool.def(false)
})
const emit = defineEmits(['toggleClick'])
const toggleClick = () => {
    emit('toggleClick');
  emit('toggleClick');
}
</script>
src/components/HeaderSearch/index.vue
@@ -17,12 +17,12 @@
  </div>
</template>
<script setup lang="ts">
import Fuse from 'fuse.js'
import { getNormalPath } from '@/utils/ruoyi'
import { isHttp } from '@/utils/validate'
import usePermissionStore from '@/store/modules/permission'
import { RouteOption } from 'vue-router'
<script setup lang="ts" name="HeaderSearch">
import Fuse from 'fuse.js';
import { getNormalPath } from '@/utils/ruoyi';
import { isHttp } from '@/utils/validate';
import usePermissionStore from '@/store/modules/permission';
import { RouteOption } from 'vue-router';
type Router = Array<{
    path: string;
@@ -34,7 +34,7 @@
const searchPool = ref<Router>([]);
const show = ref(false);
const fuse = ref();
const headerSearchSelectRef = ref(ElSelect);
const headerSearchSelectRef = ref<ElSelectInstance>();
const router = useRouter();
const routes = computed(() => usePermissionStore().routes);
@@ -123,9 +123,9 @@
    searchPool.value = generateRoutes(routes.value);
})
watchEffect(() => {
    searchPool.value = generateRoutes(routes.value)
})
// watchEffect(() => {
//     searchPool.value = generateRoutes(routes.value)
// })
watch(show, (value) => {
    if (value) {
@@ -142,40 +142,40 @@
<style lang="scss" scoped>
.header-search {
  font-size: 0 !important;
    font-size: 0 !important;
  .search-icon {
    cursor: pointer;
    font-size: 18px;
    vertical-align: middle;
  }
  .header-search-select {
    font-size: 18px;
    transition: width 0.2s;
    width: 0;
    overflow: hidden;
    background: transparent;
    border-radius: 0;
    display: inline-block;
    vertical-align: middle;
    :deep(.el-input__inner) {
      border-radius: 0;
      border: 0;
      padding-left: 0;
      padding-right: 0;
      box-shadow: none !important;
      border-bottom: 1px solid #d9d9d9;
      vertical-align: middle;
    .search-icon {
        cursor: pointer;
        font-size: 18px;
        vertical-align: middle;
    }
  }
  &.show {
    .header-search-select {
      width: 210px;
      margin-left: 10px;
        font-size: 18px;
        transition: width 0.2s;
        width: 0;
        overflow: hidden;
        background: transparent;
        border-radius: 0;
        display: inline-block;
        vertical-align: middle;
        :deep(.el-input__inner) {
            border-radius: 0;
            border: 0;
            padding-left: 0;
            padding-right: 0;
            box-shadow: none !important;
            border-bottom: 1px solid #d9d9d9;
            vertical-align: middle;
        }
    }
  }
    &.show {
        .header-search-select {
            width: 210px;
            margin-left: 10px;
        }
    }
}
</style>
src/components/IconSelect/index.vue
@@ -31,17 +31,11 @@
<script setup lang="ts">
import icons from '@/components/IconSelect/requireIcons';
import { propTypes } from '@/utils/propTypes';
const props = defineProps({
  modelValue: {
    type: String,
    require: true
  },
  width: {
    type: String,
    require: false,
    default: '400px'
  }
  modelValue: propTypes.string.isRequired,
  width: propTypes.string.def('400px')
});
const emit = defineEmits(['update:modelValue']);
src/components/ImagePreview/index.vue
@@ -9,47 +9,46 @@
</template>
<script setup lang="ts">
import { propTypes } from '@/utils/propTypes';
const props = defineProps({
    src: {
        type: String,
        default: ""
    },
    width: {
        type: [Number, String],
        default: ""
    },
    height: {
        type: [Number, String],
        default: ""
    }
  src: propTypes.string.def(''),
  width: {
    type: [Number, String],
    default: ""
  },
  height: {
    type: [Number, String],
    default: ""
  }
});
const realSrc = computed(() => {
    if (!props.src) {
        return;
    }
    let real_src = props.src.split(",")[0];
    return real_src;
  if (!props.src) {
    return;
  }
  let real_src = props.src.split(",")[0];
  return real_src;
});
const realSrcList = computed(() => {
    if (!props.src) {
        return;
    }
    let real_src_list = props.src.split(",");
    let srcList:string[] = [];
    real_src_list.forEach(item => {
        return srcList.push(item);
    });
    return srcList;
  if (!props.src) {
    return;
  }
  let real_src_list = props.src.split(",");
  let srcList: string[] = [];
  real_src_list.forEach(item => {
    return srcList.push(item);
  });
  return srcList;
});
const realWidth = computed(() =>
    typeof props.width == "string" ? props.width : `${props.width}px`
  typeof props.width == "string" ? props.width : `${props.width}px`
);
const realHeight = computed(() =>
    typeof props.height == "string" ? props.height : `${props.height}px`
  typeof props.height == "string" ? props.height : `${props.height}px`
);
</script>
@@ -58,13 +57,16 @@
  border-radius: 5px;
  background-color: #ebeef5;
  box-shadow: 0 0 5px 1px #ccc;
  :deep(.el-image__inner) {
    transition: all 0.3s;
    cursor: pointer;
    &:hover {
      transform: scale(1.2);
    }
  }
  :deep(.image-slot) {
    display: flex;
    justify-content: center;
src/components/ImageUpload/index.vue
@@ -17,7 +17,9 @@
      :on-preview="handlePictureCardPreview"
      :class="{ hide: fileList.length >= limit }"
    >
      <el-icon class="avatar-uploader-icon"><plus /></el-icon>
      <el-icon class="avatar-uploader-icon">
        <plus />
      </el-icon>
    </el-upload>
    <!-- ä¸Šä¼ æç¤º -->
    <div class="el-upload__tip" v-if="showTip">
@@ -42,25 +44,16 @@
import { listByIds, delOss } from "@/api/system/oss";
import { ComponentInternalInstance, PropType } from "vue";
import { OssVO } from "@/api/system/oss/types";
import { ElUpload, UploadFile } from "element-plus";
import { propTypes } from '@/utils/propTypes';
const props = defineProps({
    modelValue: [String, Object, Array],
    // å›¾ç‰‡æ•°é‡é™åˆ¶
    limit: {
        type: Number,
        default: 5,
    },
    limit: propTypes.number.def(5),
    // å¤§å°é™åˆ¶(MB)
    fileSize: {
        type: Number,
        default: 5,
    },
    fileSize: propTypes.number.def(5),
    // æ–‡ä»¶ç±»åž‹, ä¾‹å¦‚['png', 'jpg', 'jpeg']
    fileType: {
        type: Array as PropType<string[]>,
        default: () => ["png", "jpg", "jpeg"],
    },
    fileType: propTypes.array.def(["png", "jpg", "jpeg"]),
    // æ˜¯å¦æ˜¾ç¤ºæç¤º
    isShowTip: {
        type: Boolean,
@@ -84,12 +77,12 @@
    () => props.isShowTip && (props.fileType || props.fileSize)
);
const imageUploadRef = ref(ElUpload);
const imageUploadRef = ref<ElUploadInstance>();
watch(() => props.modelValue, async val => {
    if (val) {
        // é¦–先将值转为数组
        let list:OssVO[] = [];
        let list: OssVO[] = [];
        if (Array.isArray(val)) {
            list = val as OssVO[];
        } else {
@@ -112,7 +105,7 @@
        fileList.value = [];
        return [];
    }
},{ deep: true, immediate: true });
}, { deep: true, immediate: true });
/** ä¸Šä¼ å‰loading加载 */
const handleBeforeUpload = (file: any) => {
@@ -122,7 +115,7 @@
        if (file.name.lastIndexOf(".") > -1) {
            fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
        }
        isImg = props.fileType.some((type) => {
        isImg = props.fileType.some((type: any) => {
            if (file.type.indexOf(type) > -1) return true;
            if (fileExtension && fileExtension.indexOf(type) > -1) return true;
            return false;
@@ -161,7 +154,7 @@
        number.value--;
        proxy?.$modal.closeLoading();
        proxy?.$modal.msgError(res.msg);
        imageUploadRef.value.handleRemove(file);
        imageUploadRef.value?.handleRemove(file);
        uploadedSuccessfully();
    }
}
@@ -207,7 +200,7 @@
    let strs = "";
    separator = separator || ",";
    for (let i in list) {
        if(undefined !== list[i].ossId && list[i].url.indexOf("blob:") !== 0) {
        if (undefined !== list[i].ossId && list[i].url.indexOf("blob:") !== 0) {
            strs += list[i].ossId + separator;
        }
    }
src/components/Pagination/index.vue
@@ -22,52 +22,23 @@
<script setup lang="ts">
import { scrollTo } from '@/utils/scroll-to'
import { PropType } from "vue";
import { propTypes } from "@/utils/propTypes";
const props = defineProps({
    total: {
        required: true,
        type: Number
    },
    page: {
        type: Number,
        default: 1
    },
    limit: {
        type: Number,
        default: 20
    },
    total: propTypes.number,
    page: propTypes.number.def(1),
    limit: propTypes.number.def(20),
    pageSizes: {
        type: Array as PropType<number[]>,
        default() {
            return [10, 20, 30, 50]
        }
      type: Array as PropType<number[]>,
      default: () => [10, 20, 30, 50]
    },
    // ç§»åŠ¨ç«¯é¡µç æŒ‰é’®çš„æ•°é‡ç«¯é»˜è®¤å€¼5
    pagerCount: {
        type: Number,
        default: document.body.clientWidth < 992 ? 5 : 7
    },
    layout: {
        type: String,
        default: 'total, sizes, prev, pager, next, jumper'
    },
    background: {
        type: Boolean,
        default: true
    },
    autoScroll: {
        type: Boolean,
        default: true
    },
    hidden: {
        type: Boolean,
        default: false
    },
    float: {
        type: String,
        default: 'right'
    }
    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']);
@@ -106,7 +77,6 @@
<style lang="scss" scoped>
.pagination-container {
  background: #fff;
  padding: 32px 16px;
  .el-pagination{
    float: v-bind(float);
src/components/RightToolbar/index.vue
@@ -18,25 +18,15 @@
</template>
<script setup lang="ts">
import { TransferKey } from "element-plus";
import { PropType } from "vue";
import { propTypes } from '@/utils/propTypes';
const props = defineProps({
    showSearch: {
        type: Boolean,
        default: true,
    },
    showSearch: propTypes.bool.def(true),
    columns: {
        type: Array as PropType<FieldOption[]>,
    },
    search: {
        type: Boolean,
        default: true,
    },
    gutter: {
        type: Number,
        default: 10,
    },
    search: propTypes.bool.def(true),
    gutter: propTypes.number.def(10),
})
const emits = defineEmits(['update:showSearch', 'queryTable']);
src/components/SvgIcon/index.vue
@@ -5,19 +5,12 @@
</template>
<script setup lang="ts">
import { propTypes } from '@/utils/propTypes';
const props = defineProps({
    iconClass: {
        type: String,
        required: true
    },
    className: {
        type: String,
        default: ''
    },
    color: {
        type: String,
        default: ''
    },
    iconClass: propTypes.string.isRequired,
    className: propTypes.string.def(''),
    color: propTypes.string.def(''),
})
const iconName =  computed(() => `#icon-${props.iconClass}`);
const svgClass = computed(() => {
src/components/TreeSelect/index.vue
@@ -29,94 +29,93 @@
</template>
<script setup lang="ts">
import { ElTreeSelect } from 'element-plus'
const props = defineProps({
  /* é…ç½®é¡¹ */
  objMap: {
  type: Object,
  default: () => {
    return {
    value: 'id', // ID字段名
    label: 'label', // æ˜¾ç¤ºåç§°
    children: 'children' // å­çº§å­—段名
    type: Object,
    default: () => {
      return {
        value: 'id', // ID字段名
        label: 'label', // æ˜¾ç¤ºåç§°
        children: 'children' // å­çº§å­—段名
      }
    }
  }
  },
  /* è‡ªåŠ¨æ”¶èµ· */
  accordion: {
  type: Boolean,
  default: () => {
    return false
  }
    type: Boolean,
    default: () => {
      return false
    }
  },
  /**当前双向数据绑定的值 */
  value: {
  type: [String, Number],
  default: ''
    type: [String, Number],
    default: ''
  },
  /**当前的数据 */
  options: {
  type: Array,
  default: () => []
    type: Array,
    default: () => []
  },
  /**输入框内部的文字 */
  placeholder: {
  type: String,
  default: ''
    type: String,
    default: ''
  }
})
const selectTree = ref(ElTreeSelect);
const selectTree = ref<ElTreeSelectInstance>();
const emit = defineEmits(['update:value']);
const valueId = computed({
  get: () => props.value,
  set: (val) => {
  emit('update:value', val)
    emit('update:value', val)
  }
});
const valueTitle = ref('');
const defaultExpandedKey = ref<any[]>([]);
function initHandle() {
const initHandle = () => {
  nextTick(() => {
  const selectedValue = valueId.value;
  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] // è®¾ç½®é»˜è®¤å±•å¼€
    const selectedValue = valueId.value;
    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] // è®¾ç½®é»˜è®¤å±•å¼€
      }
    } else {
      clearHandle()
    }
  } else {
    clearHandle()
  }
  })
}
function handleNodeClick(node: any) {
const handleNodeClick = (node: any) => {
  valueTitle.value = node[props.objMap.label]
  valueId.value = node[props.objMap.value];
  defaultExpandedKey.value = [];
  selectTree.value.blur()
  selectTree.value?.blur()
  selectFilterData('')
}
function selectFilterData(val: any) {
  selectTree.value.filter(val)
const selectFilterData = (val: any) => {
  selectTree.value?.filter(val)
}
function filterNode(value: any, data: any) {
const filterNode = (value: any, data: any) => {
  if (!value) return true
  return data[props.objMap['label']].indexOf(value) !== -1
}
function clearHandle() {
const clearHandle = () => {
  valueTitle.value = ''
  valueId.value = ''
  defaultExpandedKey.value = [];
  clearSelected()
}
function clearSelected() {
const clearSelected = () => {
  const allNode = document.querySelectorAll('#tree-option .el-tree-node')
  allNode.forEach((element) => element.classList.remove('is-current'))
}
@@ -132,6 +131,7 @@
<style lang="scss" scoped>
@import "@/assets/styles/variables.module.scss";
.el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
  padding: 0;
  background-color: #fff;
src/components/iFrame/index.vue
@@ -5,11 +5,10 @@
</template>
<script setup lang="ts">
import { propTypes } from '@/utils/propTypes';
const props = defineProps({
  src: {
    type: String,
    required: true
  }
  src: propTypes.string.isRequired
})
const height = ref(document.documentElement.clientHeight - 94.5 + "px;")
src/layout/components/IframeToggle/index.vue
@@ -16,4 +16,4 @@
const route = useRoute();
const tagsViewStore = useTagsViewStore()
</script>
</script>
src/layout/components/Navbar.vue
@@ -20,8 +20,13 @@
          <template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template>
        </el-select>
        <header-search id="header-search" class="right-menu-item" />
        <!-- <header-search id="header-search" class="right-menu-item" /> -->
        <search-menu ref="searchMenuRef" />
        <el-tooltip content="搜索" effect="dark" placement="bottom">
          <div class="right-menu-item hover-effect" @click="openSearchMenu">
            <svg-icon class-name="search-icon" icon-class="search" />
          </div>
        </el-tooltip>
        <el-tooltip content="Github" effect="dark" placement="bottom">
          <ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
        </el-tooltip>
@@ -68,17 +73,18 @@
</template>
<script setup lang="ts">
import useAppStore from '@/store/modules/app'
import useUserStore from '@/store/modules/user'
import useSettingsStore from '@/store/modules/settings'
import SearchMenu from './topBar/search.vue';
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";
const appStore = useAppStore()
const userStore = useUserStore()
const settingsStore = useSettingsStore()
const appStore = useAppStore();
const userStore = useUserStore();
const settingsStore = useSettingsStore();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -89,46 +95,52 @@
const dynamic = ref(false);
// ç§Ÿæˆ·å¼€å…³
const tenantEnabled = ref(true);
// æœç´¢èœå•
const searchMenuRef = ref<InstanceType<typeof SearchMenu>>();
const openSearchMenu = () => {
  searchMenuRef.value?.openSearch();
}
// åŠ¨æ€åˆ‡æ¢
const dynamicTenantEvent = async (tenantId: string) => {
    if (companyName.value != null && companyName.value !== '') {
        await dynamicTenant(tenantId);
        dynamic.value = true;
        proxy?.$tab.closeAllPage();
        proxy?.$router.push('/');
    }
  if (companyName.value != null && companyName.value !== '') {
    await dynamicTenant(tenantId);
    dynamic.value = true;
    proxy?.$tab.closeAllPage();
    proxy?.$router.push('/');
  }
}
const dynamicClearEvent = async () => {
    await dynamicClear();
    dynamic.value = false;
    proxy?.$tab.closeAllPage();
    proxy?.$router.push('/')
  await dynamicClear();
  dynamic.value = false;
  proxy?.$tab.closeAllPage();
  proxy?.$router.push('/');
}
/** ç§Ÿæˆ·åˆ—表 */
const initTenantList = async () => {
    const { data } = await getTenantList();
    tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
    if (tenantEnabled.value) {
        tenantList.value = data.voList;
    }
  const { data } = await getTenantList();
  tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
  if (tenantEnabled.value) {
    tenantList.value = data.voList;
  }
}
defineExpose({
    initTenantList,
  initTenantList,
})
const toggleSideBar = () => {
    appStore.toggleSideBar(false)
  appStore.toggleSideBar(false);
}
const logout = async () => {
    await ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    await userStore.logout()
    location.href = import.meta.env.VITE_APP_CONTEXT_PATH + 'index';
@@ -169,7 +181,7 @@
  height: 50px;
  overflow: hidden;
  position: relative;
  background: #fff;
  //background: #fff;
  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
  .hamburger-container {
src/layout/components/Settings/index.vue
@@ -1,8 +1,7 @@
<template>
  <el-drawer v-model="showSettings" :withHeader="false" direction="rtl" size="300px" close-on-click-modal>
    <div class="setting-drawer-title">
      <h3 class="drawer-title">主题风格设置</h3>
    </div>
    <h3 class="drawer-title">主题风格设置</h3>
    <div class="setting-drawer-block-checbox">
      <div class="setting-drawer-block-checbox-item" @click="handleTheme('theme-dark')">
        <img src="@/assets/images/dark.svg" alt="dark" />
@@ -35,6 +34,13 @@
        <el-color-picker v-model="theme" :predefine="predefineColors" @change="themeChange" />
      </span>
    </div>
    <div class="drawer-item">
      <span>深色模式</span>
      <span class="comp-style">
        <el-switch v-model="isDark" @change="toggleDark" class="drawer-switch" />
      </span>
    </div>
    <el-divider />
    <h3 class="drawer-title">系统布局配置</h3>
@@ -102,7 +108,15 @@
const storeSettings = computed(() => settingsStore);
const predefineColors = ref(["#409EFF", "#ff4500", "#ff8c00", "#ffd700", "#90ee90", "#00ced1", "#1e90ff", "#c71585"]);
/** æ˜¯å¦éœ€è¦topnav */
// æ˜¯å¦æš—黑模式
const isDark = useDark({
  storageKey: 'useDarkKey',
  valueDark: 'dark',
  valueLight: 'light',
});
const toggleDark = () => useToggle(isDark);
/** æ˜¯å¦éœ€è¦topNav */
const topNav = computed({
    get: () => storeSettings.value.topNav,
    set: (val) => {
@@ -234,7 +248,6 @@
}
.drawer-item {
  color: rgba(0, 0, 0, 0.65);
  padding: 12px 0;
  font-size: 14px;
src/layout/components/TagsView/ScrollPane.vue
@@ -6,22 +6,21 @@
<script setup lang="ts">
import useTagsViewStore from '@/store/modules/tagsView'
import { ElScrollbar } from 'element-plus';
import { TagView } from 'vue-router'
const tagAndTagSpacing = ref(4);
const scrollContainerRef = ref(ElScrollbar)
const scrollWrapper = computed(() => scrollContainerRef.value.$refs.wrapRef);
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 eventDelta = (e as any).wheelDelta || - e.deltaY * 40
    const $scrollWrapper = scrollWrapper.value;
    $scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
}
@@ -34,7 +33,7 @@
const visitedViews = computed(() => tagsViewStore.visitedViews);
const moveToTarget = (currentTag: TagView) => {
    const $container = scrollContainerRef.value.$el
    const $container = scrollContainerRef.value?.$el
    const $containerWidth = $container.offsetWidth
    const $scrollWrapper = scrollWrapper.value;
src/layout/components/TagsView/index.vue
@@ -125,6 +125,9 @@
}
const addTags = () => {
    const { name } = route;
    if(route.query.title) {
        route.meta.title = route.query.title;
    }
    if (name) {
        useTagsViewStore().addView(route);
        if (route.meta.link) {
@@ -237,8 +240,8 @@
.tags-view-container {
  height: 34px;
  width: 100%;
  background: #fff;
  border-bottom: 1px solid #d8dce5;
  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);
  .tags-view-wrapper {
    .tags-view-item {
@@ -247,13 +250,16 @@
      cursor: pointer;
      height: 26px;
      line-height: 23px;
      border: 1px solid #d8dce5;
      background-color: var(--el-bg-color);
      border: 1px solid var(--el-border-color-light);
      color: #495060;
      background: #fff;
      padding: 0 8px;
      font-size: 12px;
      margin-left: 5px;
      margin-top: 4px;
      &:hover {
        color: var(--el-color-primary);
      }
      &:first-of-type {
        margin-left: 15px;
      }
@@ -279,7 +285,7 @@
  }
  .contextmenu {
    margin: 0;
    background: #fff;
    background: var(--el-bg-color);
    z-index: 3000;
    position: absolute;
    list-style-type: none;
@@ -287,7 +293,6 @@
    border-radius: 4px;
    font-size: 12px;
    font-weight: 400;
    color: #333;
    box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
    li {
      margin: 0;
src/layout/components/topBar/search.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,158 @@
<template>
  <div class="layout-search-dialog">
    <el-dialog v-model="state.isShowSearch" destroy-on-close :show-close="false">
      <template #footer>
        <el-autocomplete
          v-model="state.menuQuery"
          :fetch-suggestions="menuSearch"
          placeholder="搜索"
          ref="layoutMenuAutocompleteRef"
          @select="onHandleSelect"
          :fit-input-width="true"
        >
          <template #prefix>
            <svg-icon class-name="search-icon" icon-class="search" />
          </template>
          <template #default="{ item }">
            <div>
              <svg-icon :icon-class="item.icon" class="mr5" />
              {{ item.title }}
            </div>
          </template>
        </el-autocomplete>
      </template>
    </el-dialog>
  </div>
</template>
<script setup lang="ts" name="layoutBreadcrumbSearch">
import { getNormalPath } from '@/utils/ruoyi';
import { isHttp } from '@/utils/validate';
import usePermissionStore from '@/store/modules/permission';
import { RouteOption } from 'vue-router';
type Router = Array<{
    path: string;
    icon: string;
    title: string[];
}>
type SearchState<T = any> = {
    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: [],
});
// æœç´¢å¼¹çª—打开
const openSearch = () => {
    state.menuQuery = '';
    state.isShowSearch = true;
    state.menuList = generateRoutes(routes.value);
    nextTick(() => {
        setTimeout(() => {
            layoutMenuAutocompleteRef.value.focus();
        });
    });
};
// æœç´¢å¼¹çª—关闭
const closeSearch = () => {
    state.isShowSearch = false;
};
// èœå•搜索数据过滤
const menuSearch = (queryString: string, cb: Function) => {
    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 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();
};
// æš´éœ²å˜é‡
defineExpose({
    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%);
    }
}
</style>
src/plugins/modal.ts
@@ -1,57 +1,57 @@
import { ElMessage, ElMessageBox, ElNotification, ElLoading, MessageBoxData } from 'element-plus';
import { MessageBoxData } from 'element-plus';
import { LoadingInstance } from 'element-plus/es/components/loading/src/loading';
let loadingInstance: LoadingInstance;
export default {
  // æ¶ˆæ¯æç¤º
  msg(content: string) {
  msg(content: any) {
    ElMessage.info(content);
  },
  // é”™è¯¯æ¶ˆæ¯
  msgError(content: string) {
  msgError(content: any) {
    ElMessage.error(content);
  },
  // æˆåŠŸæ¶ˆæ¯
  msgSuccess(content: string) {
  msgSuccess(content: any) {
    ElMessage.success(content);
  },
  // è­¦å‘Šæ¶ˆæ¯
  msgWarning(content: string) {
  msgWarning(content: any) {
    ElMessage.warning(content);
  },
  // å¼¹å‡ºæç¤º
  alert(content: string) {
  alert(content: any) {
    ElMessageBox.alert(content, '系统提示');
  },
  // é”™è¯¯æç¤º
  alertError(content: string) {
  alertError(content: any) {
    ElMessageBox.alert(content, '系统提示', { type: 'error' });
  },
  // æˆåŠŸæç¤º
  alertSuccess(content: string, s: string, p: { dangerouslyUseHTMLString: boolean }) {
  alertSuccess(content: any) {
    ElMessageBox.alert(content, '系统提示', { type: 'success' });
  },
  // è­¦å‘Šæç¤º
  alertWarning(content: string) {
  alertWarning(content: any) {
    ElMessageBox.alert(content, '系统提示', { type: 'warning' });
  },
  // é€šçŸ¥æç¤º
  notify(content: string) {
  notify(content: any) {
    ElNotification.info(content);
  },
  // é”™è¯¯é€šçŸ¥
  notifyError(content: string) {
  notifyError(content: any) {
    ElNotification.error(content);
  },
  // æˆåŠŸé€šçŸ¥
  notifySuccess(content: string) {
  notifySuccess(content: any) {
    ElNotification.success(content);
  },
  // è­¦å‘Šé€šçŸ¥
  notifyWarning(content: string) {
  notifyWarning(content: any) {
    ElNotification.warning(content);
  },
  // ç¡®è®¤çª—体
  confirm(content: string): Promise<MessageBoxData> {
  confirm(content: any): Promise<MessageBoxData> {
    return ElMessageBox.confirm(content, '系统提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
@@ -59,7 +59,7 @@
    });
  },
  // æäº¤å†…容
  prompt(content: string) {
  prompt(content: any) {
    return ElMessageBox.prompt(content, '系统提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
src/plugins/tab.ts
@@ -3,8 +3,11 @@
import { TagView, RouteLocationRaw } from 'vue-router';
export default {
  // åˆ·æ–°å½“前tab页签
  async refreshPage(obj: TagView): Promise<void> {
  /**
   * åˆ·æ–°å½“前tab页签
   * @param obj æ ‡ç­¾å¯¹è±¡
   */
  async refreshPage(obj?: TagView): Promise<void> {
    const { path, query, matched } = router.currentRoute.value;
    if (obj === undefined) {
      matched.forEach((m) => {
@@ -15,11 +18,16 @@
        }
      });
    }
    // prettier-ignore
    await useTagsViewStore().delCachedView(obj)
    router.replace({
      path: '/redirect' + obj.path,
      query: obj.query
    let query1: undefined | {} = {};
    let path1: undefined | string = '';
    if (obj) {
      query1 = obj.query;
      path1 = obj.path;
    }
    await useTagsViewStore().delCachedView(obj);
    await router.replace({
      path: '/redirect' + path1,
      query: query1
    });
  },
  // å…³é—­å½“前tab页签,打开新页签
@@ -34,9 +42,9 @@
    if (obj === undefined) {
      // prettier-ignore
      const { visitedViews } = await useTagsViewStore().delView(router.currentRoute.value) as any
      const latestView = visitedViews.slice(-1)[0]
      const latestView = visitedViews.slice(-1)[0];
      if (latestView) {
        return router.push(latestView.fullPath)
        return router.push(latestView.fullPath);
      }
      return router.push('/');
    }
@@ -47,22 +55,31 @@
    return useTagsViewStore().delAllViews();
  },
  // å…³é—­å·¦ä¾§tab页签
  closeLeftPage(obj: TagView) {
  closeLeftPage(obj?: TagView) {
    return useTagsViewStore().delLeftTags(obj || router.currentRoute.value);
  },
  // å…³é—­å³ä¾§tab页签
  closeRightPage(obj: TagView) {
  closeRightPage(obj?: TagView) {
    return useTagsViewStore().delRightTags(obj || router.currentRoute.value);
  },
  // å…³é—­å…¶ä»–tab页签
  closeOtherPage(obj: TagView) {
  closeOtherPage(obj?: TagView) {
    return useTagsViewStore().delOthersViews(obj || router.currentRoute.value);
  },
  // æ‰“å¼€tab页签
  openPage(url: RouteLocationRaw) {
    return router.push(url);
  /**
   * æ‰“å¼€tab页签
   * @param url è·¯ç”±åœ°å€
   * @param title æ ‡é¢˜
   * @param query å‚æ•°
   */
  openPage(url: string, title?: string, query?: any) {
    const obj = { path: url, query: { ...query, title } };
    return router.push(obj);
  },
  // ä¿®æ”¹tab页签
  /**
   * ä¿®æ”¹tab页签
   * @param obj æ ‡ç­¾å¯¹è±¡
   */
  updatePage(obj: TagView) {
    return useTagsViewStore().updateVisitedView(obj);
  }
src/store/modules/tagsView.ts
@@ -54,8 +54,11 @@
      resolve([...visitedViews.value]);
    });
  };
  const delCachedView = (view: TagView): Promise<string[]> => {
    const viewName = view.name as string;
  const delCachedView = (view?: TagView): Promise<string[]> => {
    let viewName = '';
    if (view) {
      viewName = view.name as string;
    }
    return new Promise((resolve) => {
      const index = cachedViews.value.indexOf(viewName);
      index > -1 && cachedViews.value.splice(index, 1);
@@ -167,6 +170,7 @@
  const addCachedView = (view: TagView): void => {
    const viewName = view.name as string;
    if (!viewName) return;
    if (cachedViews.value.includes(viewName)) return;
    if (!view.meta?.noCache) {
      cachedViews.value.push(viewName);
src/types/element.d.ts
@@ -1 +1,35 @@
declare type ElTagType = '' | 'success' | 'warning' | 'info' | 'danger' | 'default' | 'primary';
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 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>;
  declare type ElInputNumberInstance = InstanceType<typeof ep.ElInputNumber>;
  declare type ElRadioInstance = InstanceType<typeof ep.ElRadio>;
  declare type ElRadioGroupInstance = InstanceType<typeof ep.ElRadioGroup>;
  declare type ElRadioButtonInstance = InstanceType<typeof ep.ElRadioButton>;
  declare type ElCheckboxInstance = InstanceType<typeof ep.ElCheckbox>;
  declare type ElCheckboxGroupInstance = InstanceType<typeof ep.ElCheckboxGroup>;
  declare type ElSwitchInstance = InstanceType<typeof ep.ElSwitch>;
  declare type ElDatePickerInstance = InstanceType<typeof ep.ElDatePicker>;
  declare type ElTimePickerInstance = InstanceType<typeof ep.ElTimePicker>;
  declare type ElTimeSelectInstance = InstanceType<typeof ep.ElTimeSelect>;
  declare type ElCascaderInstance = InstanceType<typeof ep.ElCascader>;
  declare type ElColorPickerInstance = InstanceType<typeof ep.ElColorPicker>;
  declare type ElRateInstance = InstanceType<typeof ep.ElRate>;
  declare type ElSliderInstance = InstanceType<typeof ep.ElSlider>;
  declare type ElUploadInstance = InstanceType<typeof ep.ElUpload>;
  declare type ElScrollbarInstance = InstanceType<typeof ep.ElScrollbar>;
  declare type TransferKey = ep.TransferKey;
  declare type CheckboxValueType = ep.CheckboxValueType;
  declare type ElFormRules = ep.FormRules;
  declare type DateModelType = ep.DateModelType;
  declare type UploadFile = typeof ep.UploadFile;
}
src/types/global.d.ts
@@ -1,9 +1,15 @@
import { FormRules } from 'element-plus';
import type { ComponentInternalInstance as ComponentInstance, PropType as VuePropType } from 'vue';
declare global {
  /** vue Instance */
  declare type ComponentInternalInstance = ComponentInstance;
  /**vue */
  declare type PropType<T> = VuePropType<T>;
  /**
   * ç•Œé¢å­—段隐藏属性
   */
  interface FieldOption {
  declare interface FieldOption {
    key: number;
    label: string;
    visible: boolean;
@@ -12,7 +18,7 @@
  /**
   * å¼¹çª—属性
   */
  interface DialogOption {
  declare interface DialogOption {
    /**
     * å¼¹çª—标题
     */
@@ -23,7 +29,7 @@
    visible: boolean;
  }
  interface UploadOption {
  declare interface UploadOption {
    /** è®¾ç½®ä¸Šä¼ çš„请求头部 */
    headers: { [key: string]: any };
@@ -34,7 +40,7 @@
  /**
   * å¯¼å…¥å±žæ€§
   */
  interface ImportOption extends UploadOption {
  declare interface ImportOption extends UploadOption {
    /** æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚ */
    open: boolean;
    /** å¼¹å‡ºå±‚标题 */
@@ -48,14 +54,14 @@
  /**
   * å­—典数据  æ•°æ®é…ç½®
   */
  interface DictDataOption {
  declare interface DictDataOption {
    label: string;
    value: string;
    elTagType?: ElTagType;
    elTagClass?: string;
  }
  interface BaseEntity {
  declare interface BaseEntity {
    createBy?: any;
    createDept?: any;
    createTime?: string;
@@ -68,15 +74,15 @@
   * T : è¡¨å•数据
   * D : æŸ¥è¯¢å‚æ•°
   */
  interface PageData<T, D> {
  declare interface PageData<T, D> {
    form: T;
    queryParams: D;
    rules: FormRules;
    rules: ElFormRules;
  }
  /**
   * åˆ†é¡µæŸ¥è¯¢å‚æ•°
   */
  interface PageQuery {
  declare interface PageQuery {
    pageNum: number;
    pageSize: number;
  }
src/types/module.d.ts
@@ -1,23 +1,27 @@
import modal from '@/plugins/modal';
import tab from '@/plugins/tab';
import { useDict } from '@/utils/dict';
import { addDateRange, handleTree, selectDictLabel, selectDictLabels, parseTime } from '@/utils/ruoyi';
import { getConfigKey, updateConfigByKey } from '@/api/system/config';
import { download as download1 } from '@/utils/request';
import download from '@/plugins/download';
import animate from '@/animate';
import type modal from '@/plugins/modal';
import type tab from '@/plugins/tab';
import type download from '@/plugins/download';
import type auth from '@/plugins/auth';
import type cache from '@/plugins/cache';
import type animate from '@/animate';
import type { useDict } from '@/utils/dict';
import type { addDateRange, handleTree, selectDictLabel, selectDictLabels, parseTime } from '@/utils/ruoyi';
import type { getConfigKey, updateConfigByKey } from '@/api/system/config';
import type { download as rd } from '@/utils/request';
declare module 'vue' {
  export interface ComponentCustomProperties {
declare module '@vue/runtime-core' {
  interface ComponentCustomProperties {
    // å…¨å±€æ–¹æ³•声明
    $modal: typeof modal;
    $tab: typeof tab;
    $download: typeof download;
    $auth: typeof auth;
    $cache: typeof cache;
    animate: typeof animate;
    useDict: typeof useDict;
    addDateRange: typeof addDateRange;
    download: typeof download1;
    download: typeof rd;
    handleTree: typeof handleTree;
    getConfigKey: typeof getConfigKey;
    updateConfigByKey: typeof updateConfigByKey;
src/types/router.d.ts
@@ -1,7 +1,7 @@
import { RouteRecordRaw } from 'vue-router';
declare module 'vue-router' {
  type RouteOption = {
  declare type RouteOption = {
    hidden?: boolean;
    permissions?: string[];
    roles?: string[];
@@ -16,15 +16,15 @@
    query?: string;
  } & RouteRecordRaw;
  interface _RouteLocationBase {
  declare interface _RouteLocationBase {
    children?: RouteOption[];
  }
  interface RouteLocationOptions {
  declare interface RouteLocationOptions {
    fullPath?: string;
  }
  interface TagView extends Partial<_RouteLocationBase> {
  declare interface TagView extends Partial<_RouteLocationBase> {
    title?: string;
    meta?: {
      link?: string;
src/types/vform3-builds.d.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,4 @@
declare module 'vform3-builds' {
  const content: any;
  export = content;
}
src/utils/propTypes.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
import { CSSProperties } from 'vue';
import { createTypes, VueTypeValidableDef, VueTypesInterface } from 'vue-types';
type PropTypes = VueTypesInterface & {
  readonly style: VueTypeValidableDef<CSSProperties>;
};
const propTypes = createTypes({
  func: undefined,
  bool: undefined,
  string: undefined,
  number: undefined,
  object: undefined,
  integer: undefined
}) as PropTypes;
propTypes.extend([
  {
    name: 'style',
    getter: true,
    type: [String, Object],
    default: undefined
  }
]);
export { propTypes };
src/views/demo/demo/index.vue
@@ -1,32 +1,34 @@
<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="68px">
          <el-form-item label="部门id" prop="deptId">
            <el-input v-model="queryParams.deptId" placeholder="请输入部门id" clearable @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="用户id" prop="userId">
            <el-input v-model="queryParams.userId" placeholder="请输入用户id" clearable @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="排序号" prop="orderNum">
            <el-input v-model="queryParams.orderNum" placeholder="请输入排序号" clearable @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="key键" prop="testKey">
            <el-input v-model="queryParams.testKey" placeholder="请输入key键" clearable @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="值" prop="value">
            <el-input v-model="queryParams.value" placeholder="请输入值" clearable @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      <div class="mb-[10px]" v-show="showSearch">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
            <el-form-item label="部门id" prop="deptId">
              <el-input v-model="queryParams.deptId" placeholder="请输入部门id" clearable @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="用户id" prop="userId">
              <el-input v-model="queryParams.userId" placeholder="请输入用户id" clearable @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="排序号" prop="orderNum">
              <el-input v-model="queryParams.orderNum" placeholder="请输入排序号" clearable @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="key键" prop="testKey">
              <el-input v-model="queryParams.testKey" placeholder="请输入key键" clearable @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="值" prop="value">
              <el-input v-model="queryParams.value" placeholder="请输入值" clearable @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item>
              <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>
      </div>
    </transition>
    <el-card shadow="never">
    <el-card shadow="hover">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
@@ -36,7 +38,9 @@
            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['demo:demo:edit']">修改</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>
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['demo:demo:remove']"
              >删除</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>
@@ -65,13 +69,7 @@
        </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" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹æµ‹è¯•å•å¯¹è¯æ¡† -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
@@ -105,8 +103,6 @@
<script setup name="Demo" lang="ts">
import { listDemo, getDemo, delDemo, addDemo, updateDemo } from '@/api/demo/demo';
import { DemoVO, DemoQuery, DemoForm } from '@/api/demo/demo/types';
import { ComponentInternalInstance } from 'vue';
import { ElForm } from 'element-plus';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -119,8 +115,8 @@
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref(ElForm);
const demoFormRef = ref(ElForm);
const queryFormRef = ref<ElFormInstance>();
const demoFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
  visible: false,
@@ -136,7 +132,7 @@
  value: undefined,
}
const data = reactive<PageData<DemoForm, DemoQuery>>({
  form: {...initFormData},
  form: { ...initFormData },
  queryParams: {
    pageNum: 1,
    pageSize: 10,
@@ -187,8 +183,8 @@
/** è¡¨å•重置 */
const reset = () => {
  form.value = {...initFormData};
  demoFormRef.value.resetFields();
  form.value = { ...initFormData };
  demoFormRef.value?.resetFields();
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
@@ -199,7 +195,7 @@
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value.resetFields();
  queryFormRef.value?.resetFields();
  handleQuery();
}
@@ -235,13 +231,13 @@
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  demoFormRef.value.validate(async (valid: boolean) => {
  demoFormRef.value?.validate(async (valid: boolean) => {
    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("修改成功");
      dialog.visible = false;
src/views/demo/tree/index.vue
@@ -1,20 +1,22 @@
<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="68px">
          <el-form-item label="树节点名" prop="treeName">
            <el-input v-model="queryParams.treeName" placeholder="请输入树节点名" clearable @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      <div class="mb-[10px]" v-show="showSearch">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
            <el-form-item label="树节点名" prop="treeName">
              <el-input v-model="queryParams.treeName" placeholder="请输入树节点名" clearable @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item>
              <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>
      </div>
    </transition>
    <el-card shadow="never">
    <el-card shadow="hover">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
@@ -89,8 +91,6 @@
<script setup name="Tree" lang="ts">
import { listTree, getTree, delTree, addTree, updateTree } from "@/api/demo/tree";
import { TreeVO, TreeQuery, TreeForm } from '@/api/demo/tree/types';
import { ComponentInternalInstance } from 'vue';
import { ElForm, ElTable } from 'element-plus';
type TreeOption = {
@@ -109,9 +109,9 @@
const isExpandAll = ref(true);
const loading = ref(false);
const queryFormRef = ref(ElForm);
const treeFormRef = ref(ElForm);
const treeTableRef = ref(ElTable)
const queryFormRef = ref<ElFormInstance>();
const treeFormRef = ref<ElFormInstance>();
const treeTableRef = ref<ElTableInstance>()
const dialog = reactive<DialogOption>({
    visible: false,
@@ -185,7 +185,7 @@
// è¡¨å•重置
const reset = () => {
  form.value = {...initFormData}
  treeFormRef.value.resetFields();
  treeFormRef.value?.resetFields();
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
@@ -195,7 +195,7 @@
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
  queryFormRef.value.resetFields();
  queryFormRef.value?.resetFields();
  handleQuery();
}
@@ -223,7 +223,7 @@
/** å±•å¼€/折叠操作 */
const toggleExpandAll = (data: TreeVO[], status: boolean) => {
  data.forEach((item) => {
    treeTableRef.value.toggleRowExpansion(item, status)
    treeTableRef.value?.toggleRowExpansion(item, status)
    if (item.children && item.children.length > 0) toggleExpandAll(item.children, status)
  })
}
@@ -247,7 +247,7 @@
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  treeFormRef.value.validate(async (valid: boolean) => {
  treeFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      buttonLoading.value = true;
      if (form.value.id) {
@@ -257,7 +257,7 @@
      }
      proxy?.$modal.msgSuccess("操作成功");
      dialog.visible = false;
      getList();
      await getList();
    }
  });
}
src/views/error/401.vue
@@ -21,7 +21,6 @@
<script setup lang="ts">
import errImage from '@/assets/401_images/401.gif';
import { ComponentInternalInstance } from "vue";
let { proxy } = getCurrentInstance() as ComponentInternalInstance;
src/views/login.vue
@@ -65,14 +65,14 @@
import { encrypt, decrypt } from '@/utils/jsencrypt';
import { useUserStore } from '@/store/modules/user';
import { LoginData, TenantVO } from '@/api/types';
import { ElForm, FormRules } from 'element-plus';
import { to } from 'await-to-js';
import { HttpStatus } from "@/enums/RespEnum";
const userStore = useUserStore();
const router = useRouter();
const loginForm = ref<LoginData>({
  tenantId: "000000",
  tenantId: '000000',
  username: 'admin',
  password: 'admin123',
  rememberMe: false,
@@ -80,7 +80,7 @@
  uuid: ''
});
const loginRules: FormRules = {
const loginRules: ElFormRules = {
  tenantId: [{ required: true, trigger: "blur", message: "请输入您的租户编号" }],
  username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
  password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }],
@@ -98,12 +98,12 @@
// æ³¨å†Œå¼€å…³
const register = ref(false);
const redirect = ref(undefined);
const loginRef = ref(ElForm);
const loginRef = ref<ElFormInstance>();
// ç§Ÿæˆ·åˆ—表
const tenantList = ref<TenantVO[]>([]);
const handleLogin = () => {
  loginRef.value.validate(async (valid: boolean, fields: any) => {
  loginRef.value?.validate(async (valid: boolean, fields: any) => {
    if (valid) {
      loading.value = true;
      // å‹¾é€‰äº†éœ€è¦è®°ä½å¯†ç è®¾ç½®åœ¨ cookie ä¸­è®¾ç½®è®°ä½ç”¨æˆ·åå’Œå¯†ç 
@@ -120,7 +120,6 @@
        Cookies.remove('rememberMe');
      }
      // è°ƒç”¨action的登录方法
      // prittier-ignore
      const [err] = await to(userStore.login(loginForm.value));
      if (!err) {
        await router.push({ path: redirect.value || '/' });
@@ -189,7 +188,7 @@
 */
const doSocialLogin = (type: string) => {
  authBinding(type).then((res: any) => {
    if (res.code === 200) {
    if (res.code === HttpStatus.SUCCESS) {
      // èŽ·å–æŽˆæƒåœ°å€è·³è½¬
      window.location.href = res.data;
    } else {
src/views/monitor/cache/index.vue
@@ -2,7 +2,7 @@
  <div class="p-2">
    <el-row>
      <el-col :span="24" class="card-box">
        <el-card>
        <el-card shadow="hover">
          <template #header>
            <Monitor style="width: 1em; height: 1em; vertical-align: middle;" />
            <span style="vertical-align: middle;">基本信息</span>
@@ -98,7 +98,7 @@
      </el-col>
      <el-col :span="12" class="card-box">
        <el-card>
        <el-card shadow="hover">
          <template #header>
            <PieChart style="width: 1em; height: 1em; vertical-align: middle;" />
            <span style="vertical-align: middle;">命令统计</span>
@@ -110,7 +110,7 @@
      </el-col>
      <el-col :span="12" class="card-box">
        <el-card>
        <el-card shadow="hover">
          <template #header>
            <Odometer style="width: 1em; height: 1em; vertical-align: middle;" /> <span style="vertical-align: middle;">内存信息</span>
          </template>
@@ -126,7 +126,6 @@
<script setup name="Cache" lang="ts">
import { getCache } from '@/api/monitor/cache';
import * as echarts from 'echarts';
import { ComponentInternalInstance } from "vue";
const cache = ref<any>({});
const commandstats = ref();
src/views/monitor/logininfor/index.vue
@@ -1,39 +1,41 @@
<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="68px">
          <el-form-item label="登录地址" prop="ipaddr">
            <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-form-item>
          <el-form-item label="状态" prop="status">
            <el-select v-model="queryParams.status" placeholder="登录状态" clearable style="width: 240px">
              <el-option v-for="dict in sys_common_status" :key="dict.value" :label="dict.label" :value="dict.value" />
            </el-select>
          </el-form-item>
          <el-form-item label="登录时间" style="width: 308px">
            <el-date-picker
              v-model="dateRange"
              value-format="YYYY-MM-DD HH:mm:ss"
              type="daterange"
              range-separator="-"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
            ></el-date-picker>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      <div class="mb-[10px]" v-show="showSearch">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :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-form-item>
            <el-form-item label="用户名称" prop="userName">
              <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">
                <el-option v-for="dict in sys_common_status" :key="dict.value" :label="dict.label" :value="dict.value" />
              </el-select>
            </el-form-item>
            <el-form-item label="登录时间" style="width: 308px">
              <el-date-picker
                v-model="dateRange"
                value-format="YYYY-MM-DD HH:mm:ss"
                type="daterange"
                range-separator="-"
                start-placeholder="开始日期"
                end-placeholder="结束日期"
                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
              ></el-date-picker>
            </el-form-item>
            <el-form-item>
              <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>
      </div>
    </transition>
    <el-card shadow="never">
    <el-card shadow="hover">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
@@ -98,9 +100,7 @@
<script setup name="Logininfor" lang="ts">
import { list, delLoginInfo, cleanLoginInfo, unlockLoginInfo } from "@/api/monitor/loginInfo";
import { ComponentInternalInstance } from "vue";
import { LoginInfoQuery, LoginInfoVO } from "@/api/monitor/loginInfo/types";
import { DateModelType } from 'element-plus';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_common_status } = toRefs<any>(proxy?.useDict("sys_common_status"));
@@ -116,8 +116,8 @@
const dateRange = ref<[DateModelType,DateModelType]>(['', '']);
const defaultSort = ref<any>({ prop: "loginTime", order: "descending" });
const queryFormRef = ref(ElForm);
const loginInfoTableRef = ref(ElTable);
const queryFormRef = ref<ElFormInstance>();
const loginInfoTableRef = ref<ElTableInstance>();
// æŸ¥è¯¢å‚æ•°
const queryParams = ref<LoginInfoQuery>({
    pageNum: 1,
@@ -145,9 +145,9 @@
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
    dateRange.value = ['', ''];
    queryFormRef.value.resetFields();
    queryFormRef.value?.resetFields();
    queryParams.value.pageNum = 1;
    loginInfoTableRef.value.sort(defaultSort.value.prop, defaultSort.value.order);
    loginInfoTableRef.value?.sort(defaultSort.value.prop, defaultSort.value.order);
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: LoginInfoVO[]) => {
@@ -167,14 +167,14 @@
    const infoIds = row?.infoId || ids.value;
    await proxy?.$modal.confirm('是否确认删除访问编号为"' + infoIds + '"的数据项?');
    await delLoginInfo(infoIds);
    getList();
    await getList();
    proxy?.$modal.msgSuccess("删除成功");
}
/** æ¸…空按钮操作 */
const handleClean = async () => {
    await proxy?.$modal.confirm("是否确认清空所有登录日志数据项?");
    await cleanLoginInfo();
    getList();
    await getList();
    proxy?.$modal.msgSuccess("清空成功");
}
/** è§£é”æŒ‰é’®æ“ä½œ */
src/views/monitor/online/index.vue
@@ -1,20 +1,22 @@
<template>
  <div class="p-2">
    <div class="search">
      <el-form :model="queryParams" ref="queryFormRef" :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>
        <el-form-item label="用户名称" prop="userName">
          <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
          <el-button icon="Refresh" @click="resetQuery">重置</el-button>
        </el-form-item>
      </el-form>
    <div class="mb-[10px]">
      <el-card shadow="hover">
        <el-form :model="queryParams" ref="queryFormRef" :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>
          <el-form-item label="用户名称" prop="userName">
            <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item>
            <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>
    </div>
    <div class="panel">
    <el-card shadow="hover">
      <el-table
        v-loading="loading"
        :data="onlineList.slice((queryParams.pageNum - 1) * queryParams.pageSize, queryParams.pageNum * queryParams.pageSize)"
@@ -48,13 +50,12 @@
      </el-table>
      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" />
    </div>
    </el-card>
  </div>
</template>
<script setup name="Online" lang="ts">
import { forceLogout, list as initData } from "@/api/monitor/online";
import { ComponentInternalInstance } from "vue";
import { OnlineQuery, OnlineVO } from "@/api/monitor/online/types";
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -63,42 +64,42 @@
const loading = ref(true);
const total = ref(0);
const queryFormRef = ref(ElForm);
const queryFormRef = ref<ElFormInstance>();
const queryParams = ref<OnlineQuery>({
    pageNum: 1,
    pageSize: 10,
    ipaddr: '',
    userName: ''
  pageNum: 1,
  pageSize: 10,
  ipaddr: '',
  userName: ''
});
/** æŸ¥è¯¢ç™»å½•日志列表 */
const getList = async () => {
    loading.value = true;
    const res = await initData(queryParams.value);
    onlineList.value = res.rows;
    total.value = res.total;
    loading.value = false;
  loading.value = true;
  const res = await initData(queryParams.value);
  onlineList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    queryParams.value.pageNum = 1;
    getList();
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
    queryFormRef.value.resetFields();
    handleQuery();
  queryFormRef.value?.resetFields();
  handleQuery();
}
/** å¼ºé€€æŒ‰é’®æ“ä½œ */
const handleForceLogout = async (row: OnlineVO) => {
    await proxy?.$modal.confirm('是否确认强退名称为"' + row.userName + '"的用户?');
    await forceLogout(row.tokenId);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
  await proxy?.$modal.confirm('是否确认强退名称为"' + row.userName + '"的用户?');
  await forceLogout(row.tokenId);
  getList();
  proxy?.$modal.msgSuccess("删除成功");
}
onMounted(() => {
    getList();
  getList();
})
</script>
src/views/monitor/operlog/index.vue
@@ -1,44 +1,46 @@
<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="68px">
          <el-form-item label="系统模块" prop="title">
            <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-form-item>
          <el-form-item label="类型" prop="businessType">
            <el-select v-model="queryParams.businessType" placeholder="操作类型" clearable style="width: 240px">
              <el-option v-for="dict in sys_oper_type" :key="dict.value" :label="dict.label" :value="dict.value" />
            </el-select>
          </el-form-item>
          <el-form-item label="状态" prop="status">
            <el-select v-model="queryParams.status" placeholder="操作状态" clearable style="width: 240px">
              <el-option v-for="dict in sys_common_status" :key="dict.value" :label="dict.label" :value="dict.value" />
            </el-select>
          </el-form-item>
          <el-form-item label="操作时间" style="width: 308px">
            <el-date-picker
              v-model="dateRange"
              value-format="YYYY-MM-DD HH:mm:ss"
              type="daterange"
              range-separator="-"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
            ></el-date-picker>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      <div class="mb-[10px]">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
            <el-form-item label="系统模块" prop="title">
              <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-form-item>
            <el-form-item label="类型" prop="businessType">
              <el-select v-model="queryParams.businessType" placeholder="操作类型" clearable style="width: 240px">
                <el-option v-for="dict in sys_oper_type" :key="dict.value" :label="dict.label" :value="dict.value" />
              </el-select>
            </el-form-item>
            <el-form-item label="状态" prop="status">
              <el-select v-model="queryParams.status" placeholder="操作状态" clearable style="width: 240px">
                <el-option v-for="dict in sys_common_status" :key="dict.value" :label="dict.label" :value="dict.value" />
              </el-select>
            </el-form-item>
            <el-form-item label="操作时间" style="width: 308px">
              <el-date-picker
                v-model="dateRange"
                value-format="YYYY-MM-DD HH:mm:ss"
                type="daterange"
                range-separator="-"
                start-placeholder="开始日期"
                end-placeholder="结束日期"
                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
              ></el-date-picker>
            </el-form-item>
            <el-form-item>
              <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>
      </div>
    </transition>
    <el-card shadow="never">
    <el-card shadow="hover">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
@@ -132,7 +134,7 @@
            <el-form-item label="操作方法:">{{ form.method }}</el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="请求参数:">{{form.operParam}}</el-form-item>
            <el-form-item label="请求参数:">{{ form.operParam }}</el-form-item>
          </el-col>
          <el-col :span="24">
            <el-form-item label="返回参数:">{{ form.jsonResult }}</el-form-item>
@@ -165,12 +167,10 @@
<script setup name="Operlog" lang="ts">
import { list, delOperlog, cleanOperlog } from '@/api/monitor/operlog';
import { ComponentInternalInstance } from 'vue';
import { OperLogForm, OperLogQuery, OperLogVO } from '@/api/monitor/operlog/types';
import { DateModelType } from 'element-plus';
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);
@@ -181,116 +181,116 @@
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const defaultSort = ref<any>({ prop: "operTime", order: "descending" });
const operLogTableRef = ref(ElTable);
const queryFormRef = ref(ElForm);
const operLogTableRef = ref<ElTableInstance>();
const queryFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
    visible: false,
    title: ''
  visible: false,
  title: ''
});
const data = reactive<PageData<OperLogForm, OperLogQuery>>({
    form: {
        operId: undefined,
        tenantId: undefined,
        title: '',
        businessType: 0,
        businessTypes: undefined,
        method: '',
        requestMethod: '',
        operatorType: 0,
        operName: '',
        deptName: '',
        operUrl: '',
        operIp: '',
        operLocation: '',
        operParam: '',
        jsonResult: '',
        status: 0,
        errorMsg: '',
        operTime: '',
        costTime: 0
    },
    queryParams: {
        pageNum: 1,
        pageSize: 10,
        title: '',
        operName: '',
        businessType: '',
        status: '',
        orderByColumn: defaultSort.value.prop,
        isAsc: defaultSort.value.order
    },
    rules: {}
  form: {
    operId: undefined,
    tenantId: undefined,
    title: '',
    businessType: 0,
    businessTypes: undefined,
    method: '',
    requestMethod: '',
    operatorType: 0,
    operName: '',
    deptName: '',
    operUrl: '',
    operIp: '',
    operLocation: '',
    operParam: '',
    jsonResult: '',
    status: 0,
    errorMsg: '',
    operTime: '',
    costTime: 0
  },
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    title: '',
    operName: '',
    businessType: '',
    status: '',
    orderByColumn: defaultSort.value.prop,
    isAsc: defaultSort.value.order
  },
  rules: {}
});
const { queryParams, form } = toRefs(data);
/** æŸ¥è¯¢ç™»å½•日志 */
const getList = async () => {
    loading.value = true;
    const res = await list(proxy?.addDateRange(queryParams.value, dateRange.value));
    operlogList.value = res.rows;
    total.value = res.total;
    loading.value = false;
  loading.value = true;
  const res = await list(proxy?.addDateRange(queryParams.value, dateRange.value));
  operlogList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
/** æ“ä½œæ—¥å¿—类型字典翻译 */
const typeFormat = (row: OperLogForm) => {
    return proxy?.selectDictLabel(sys_oper_type.value, row.businessType);
  return proxy?.selectDictLabel(sys_oper_type.value, row.businessType);
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    queryParams.value.pageNum = 1;
    getList();
  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);
  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);
    multiple.value = !selection.length;
  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();
  queryParams.value.orderByColumn = column.prop;
  queryParams.value.isAsc = column.order;
  getList();
}
/** è¯¦ç»†æŒ‰é’®æ“ä½œ */
const handleView = (row: OperLogVO) => {
    dialog.visible = true;
    form.value = row;
  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);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
  const operIds = row?.operId || ids.value;
  await proxy?.$modal.confirm('是否确认删除日志编号为"' + operIds + '"的数据项?');
  await delOperlog(operIds);
  await getList();
  proxy?.$modal.msgSuccess("删除成功");
}
/** æ¸…空按钮操作 */
const handleClean = async () => {
    await proxy?.$modal.confirm("是否确认清空所有操作日志数据项?");
    await cleanOperlog();
    getList();
    proxy?.$modal.msgSuccess("清空成功");
  await proxy?.$modal.confirm("是否确认清空所有操作日志数据项?");
  await cleanOperlog();
  await getList();
  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();
  getList();
})
</script>
src/views/register.vue
@@ -58,19 +58,18 @@
<script setup lang="ts">
import { getCodeImg, register, getTenantList } from '@/api/login';
import { RegisterForm, TenantVO } from '@/api/types';
import { FormRules } from 'element-plus';
import { to } from 'await-to-js';
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"
});
// ç§Ÿæˆ·å¼€å…³
@@ -78,30 +77,30 @@
const equalToPassword = (rule: any, value: string, callback: any) => {
    if (registerForm.value.password !== value) {
        callback(new Error("两次输入的密码不一致"));
    } else {
        callback();
    }
  if (registerForm.value.password !== value) {
    callback(new Error("两次输入的密码不一致"));
  } else {
    callback();
  }
};
const registerRules: FormRules = {
    tenantId: [
        { required: true, trigger: "blur", message: "请输入您的租户编号" }
    ],
    username: [
        { 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" }
    ],
    confirmPassword: [
        { required: true, trigger: "blur", message: "请再次输入您的密码" },
        { required: true, validator: equalToPassword, trigger: "blur" }
    ],
    code: [{ required: true, trigger: "change", message: "请输入验证码" }]
const registerRules: ElFormRules = {
  tenantId: [
    { required: true, trigger: "blur", message: "请输入您的租户编号" }
  ],
  username: [
    { 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" }
  ],
  confirmPassword: [
    { required: true, trigger: "blur", message: "请再次输入您的密码" },
    { required: true, validator: equalToPassword, trigger: "blur" }
  ],
  code: [{ required: true, trigger: "change", message: "请输入验证码" }]
};
const codeUrl = ref("");
const loading = ref(false);
@@ -111,50 +110,50 @@
const tenantList = ref<TenantVO[]>([]);
const handleRegister = () => {
    registerRef.value.validate(async (valid: boolean) => {
        if (valid) {
            loading.value = true;
            const [err] = await to(register(registerForm.value));
            if (!err) {
                const username = registerForm.value.username;
                await ElMessageBox.alert("<font color='red'>恭喜你,您的账号 " + username + " æ³¨å†ŒæˆåŠŸï¼</font>", "系统提示", {
                    dangerouslyUseHTMLString: true,
                    type: "success",
                });
                await router.push("/login");
            } else {
                loading.value = false;
                if (captchaEnabled) {
                    getCode();
                }
            }
  registerRef.value.validate(async (valid: boolean) => {
    if (valid) {
      loading.value = true;
      const [err] = await to(register(registerForm.value));
      if (!err) {
        const username = registerForm.value.username;
        await ElMessageBox.alert("<font color='red'>恭喜你,您的账号 " + username + " æ³¨å†ŒæˆåŠŸï¼</font>", "系统提示", {
          dangerouslyUseHTMLString: true,
          type: "success",
        });
        await router.push("/login");
      } else {
        loading.value = false;
        if (captchaEnabled) {
          getCode();
        }
    });
      }
    }
  });
}
const getCode = async () => {
    const { data } = await getCodeImg();
    captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled;
    if (captchaEnabled.value) {
        codeUrl.value = "data:image/gif;base64," + data.img;
        registerForm.value.uuid = data.uuid;
    }
  const { data } = await getCodeImg();
  captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled;
  if (captchaEnabled.value) {
    codeUrl.value = "data:image/gif;base64," + data.img;
    registerForm.value.uuid = data.uuid;
  }
}
const initTenantList = async () => {
    const { data } = await getTenantList();
    tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
    if (tenantEnabled.value) {
        tenantList.value = data.voList;
        if (tenantList.value != null && tenantList.value.length !== 0) {
            registerForm.value.tenantId = tenantList.value[0].tenantId;
        }
  const { data } = await getTenantList();
  tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
  if (tenantEnabled.value) {
    tenantList.value = data.voList;
    if (tenantList.value != null && tenantList.value.length !== 0) {
      registerForm.value.tenantId = tenantList.value[0].tenantId;
    }
  }
}
onMounted(() => {
    getCode();
    initTenantList();
  getCode();
  initTenantList();
})
</script>
src/views/system/config/index.vue
@@ -1,38 +1,40 @@
<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="68px">
          <el-form-item label="参数名称" prop="configName">
            <el-input v-model="queryParams.configName" placeholder="请输入参数名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="参数键名" prop="configKey">
            <el-input v-model="queryParams.configKey" placeholder="请输入参数键名" clearable style="width: 240px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="系统内置" prop="configType">
            <el-select v-model="queryParams.configType" placeholder="系统内置" clearable>
              <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-date-picker
              v-model="dateRange"
              value-format="YYYY-MM-DD HH:mm:ss"
              type="daterange"
              range-separator="-"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
            ></el-date-picker>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      <div class="mb-[10px]" v-show="showSearch">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :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>
            <el-form-item label="参数键名" prop="configKey">
              <el-input v-model="queryParams.configKey" placeholder="请输入参数键名" clearable style="width: 240px" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="系统内置" prop="configType">
              <el-select v-model="queryParams.configType" placeholder="系统内置" clearable>
                <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-date-picker
                v-model="dateRange"
                value-format="YYYY-MM-DD HH:mm:ss"
                type="daterange"
                range-separator="-"
                start-placeholder="开始日期"
                end-placeholder="结束日期"
                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
              ></el-date-picker>
            </el-form-item>
            <el-form-item>
              <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>
      </div>
    </transition>
    <el-card shadow="never">
    <el-card shadow="hover">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
@@ -123,8 +125,6 @@
<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 { ComponentInternalInstance } from "vue";
import { DateModelType } from 'element-plus';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_yes_no } = toRefs<any>(proxy?.useDict("sys_yes_no"));
@@ -138,124 +138,124 @@
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const queryFormRef = ref(ElForm);
const configFormRef = ref(ElForm);
const queryFormRef = ref<ElFormInstance>();
const configFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
    visible: false,
    title: ''
  visible: false,
  title: ''
});
const initFormData: ConfigForm = {
    configId: undefined,
    configName: '',
    configKey: '',
    configValue: '',
    configType: "Y",
    remark: ''
  configId: undefined,
  configName: '',
  configKey: '',
  configValue: '',
  configType: "Y",
  remark: ''
}
const data = reactive<PageData<ConfigForm, ConfigQuery>>({
    form: {...initFormData},
    queryParams: {
        pageNum: 1,
        pageSize: 10,
        configName: '',
        configKey: '',
        configType: '',
    },
    rules: {
        configName: [{ required: true, message: "参数名称不能为空", trigger: "blur" }],
        configKey: [{ required: true, message: "参数键名不能为空", trigger: "blur" }],
        configValue: [{ required: true, message: "参数键值不能为空", trigger: "blur" }]
    }
  form: { ...initFormData },
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    configName: '',
    configKey: '',
    configType: '',
  },
  rules: {
    configName: [{ required: true, message: "参数名称不能为空", trigger: "blur" }],
    configKey: [{ required: true, message: "参数键名不能为空", trigger: "blur" }],
    configValue: [{ required: true, message: "参数键值不能为空", trigger: "blur" }]
  }
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢å‚数列表 */
const getList = async () => {
    loading.value = true;
    const res = await listConfig(proxy?.addDateRange(queryParams.value, dateRange.value));
    configList.value = res.rows;
    total.value = res.total;
    loading.value = false;
  loading.value = true;
  const res = await listConfig(proxy?.addDateRange(queryParams.value, dateRange.value));
  configList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
/** å–消按钮 */
const cancel = () => {
    reset();
    dialog.visible = false;
  reset();
  dialog.visible = false;
}
/** è¡¨å•重置 */
const reset = () => {
    form.value = {...initFormData};
    configFormRef.value.resetFields();
  form.value = { ...initFormData };
  configFormRef.value?.resetFields();
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    queryParams.value.pageNum = 1;
    getList();
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
    dateRange.value = ['', ''];
    queryFormRef.value.resetFields();
    handleQuery();
  dateRange.value = ['', ''];
  queryFormRef.value?.resetFields();
  handleQuery();
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: ConfigVO[]) => {
    ids.value = selection.map(item => item.configId);
    single.value = selection.length != 1;
    multiple.value = !selection.length;
  ids.value = selection.map(item => item.configId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
    dialog.visible = true;
    dialog.title = "添加参数";
    nextTick(() => {
        reset();
    })
  dialog.visible = true;
  dialog.title = "添加参数";
  nextTick(() => {
    reset();
  })
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = (row?: ConfigVO) => {
    dialog.visible = true;
    dialog.title = "修改参数";
    const configId = row?.configId || ids.value[0];
    nextTick(async () => {
        reset();
        const res = await getConfig(configId);
        form.value = res.data;
    })
  dialog.visible = true;
  dialog.title = "修改参数";
  const configId = row?.configId || ids.value[0];
  nextTick(async () => {
    reset();
    const res = await getConfig(configId);
    form.value = res.data;
  })
}
/** æäº¤æŒ‰é’® */
const submitForm = () => {
    configFormRef.value.validate(async (valid: boolean) => {
        if (valid) {
            form.value.configId ? await updateConfig(form.value) : await addConfig(form.value);
            proxy?.$modal.msgSuccess("操作成功");
            dialog.visible = false;
            getList();
        }
    });
  configFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      form.value.configId ? await updateConfig(form.value) : await addConfig(form.value);
      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);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
  const configIds = row?.configId || ids.value;
  await proxy?.$modal.confirm('是否确认删除参数编号为"' + configIds + '"的数据项?');
  await delConfig(configIds);
  await getList();
  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("刷新缓存成功");
  await refreshCache();
  proxy?.$modal.msgSuccess("刷新缓存成功");
}
onMounted(() => {
    getList();
  getList();
})
</script>
src/views/system/dept/index.vue
@@ -1,25 +1,27 @@
<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 ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
          <el-form-item label="菜单名称" prop="menuName">
            <el-input v-model="queryParams.deptName" placeholder="请输入部门名称" clearable @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="状态" prop="status">
            <el-select v-model="queryParams.status" placeholder="部门状态" clearable>
              <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>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      <div class="mb-[10px]" v-show="showSearch">
        <el-card shadow="hover">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
            <el-form-item label="菜单名称" prop="menuName">
              <el-input v-model="queryParams.deptName" placeholder="请输入部门名称" clearable @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="状态" prop="status">
              <el-select v-model="queryParams.status" placeholder="部门状态" clearable>
                <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>
              <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>
      </div>
    </transition>
    <el-card shadow="never">
    <el-card shadow="hover">
      <template #header>
        <el-row :gutter="10">
          <el-col :span="1.5">
@@ -130,13 +132,12 @@
<script setup name="Dept" lang="ts">
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from "@/api/system/dept"
import { ComponentInternalInstance } from 'vue';
import { DeptForm, DeptQuery, DeptVO } from "@/api/system/dept/types";
interface DeptOptionsType {
    deptId: number | string;
    deptName: string;
    children: DeptOptionsType[];
  deptId: number | string;
  deptName: string;
  children: DeptOptionsType[];
}
@@ -151,142 +152,146 @@
const dialog = reactive<DialogOption>({
    visible: false,
    title: ''
  visible: false,
  title: ''
});
const deptTableRef = ref(ElTable);
const queryFormRef = ref(ElForm);
const deptFormRef = ref(ElForm);
const deptTableRef = ref<ElTableInstance>();
const queryFormRef = ref<ElFormInstance>();
const deptFormRef = ref<ElFormInstance>();
const initFormData: DeptForm = {
    deptId: undefined,
    parentId: undefined,
    deptName: undefined,
    orderNum: 0,
    leader: undefined,
    phone: undefined,
    email: undefined,
    status: "0"
  deptId: undefined,
  parentId: undefined,
  deptName: undefined,
  orderNum: 0,
  leader: undefined,
  phone: undefined,
  email: undefined,
  status: "0"
}
const data = reactive<PageData<DeptForm, DeptQuery>>({
    form: {...initFormData},
    queryParams: {
        pageNum: 1,
        pageSize: 10,
        deptName: undefined,
        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" }]
    },
  form: { ...initFormData },
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    deptName: undefined,
    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" }]
  },
})
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")
    if (data) {
        deptList.value = data
    }
    loading.value = false
  loading.value = true;
  const res = await listDept(queryParams.value);
  const data = proxy?.handleTree<DeptVO>(res.data, "deptId")
  if (data) {
    deptList.value = data
  }
  loading.value = false
}
/** å–消按钮 */
const cancel = () => {
    reset()
    dialog.visible = false
  reset()
  dialog.visible = false
}
/** è¡¨å•重置 */
const reset = () => {
    form.value = {...initFormData};
    deptFormRef.value.resetFields();
  form.value = { ...initFormData };
  deptFormRef.value?.resetFields();
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    getList();
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
    queryFormRef.value.resetFields();
    handleQuery()
  queryFormRef.value?.resetFields();
  handleQuery()
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = (row?: DeptVO) => {
    listDept().then(res => {
        const data = proxy?.handleTree<DeptOptionsType>(res.data, "deptId");
        if (data) {
            deptOptions.value = data
            dialog.visible = true;
            dialog.title = "添加部门";
            nextTick(() => {
                reset();
                if (row && row.deptId) {
                    form.value.parentId = row?.deptId;
                }
            })
  listDept().then(res => {
    const data = proxy?.handleTree<DeptOptionsType>(res.data, "deptId");
    if (data) {
      deptOptions.value = data
      dialog.visible = true;
      dialog.title = "添加部门";
      nextTick(() => {
        reset();
        if (row && row.deptId) {
          form.value.parentId = row?.deptId;
        }
    })
      })
    }
  })
}
/** å±•å¼€/折叠操作 */
const handleToggleExpandAll = () => {
    isExpandAll.value = !isExpandAll.value;
    toggleExpandAll(deptList.value, isExpandAll.value)
  isExpandAll.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)
    })
  data.forEach((item) => {
    deptTableRef.value?.toggleRowExpansion(item, status)
    if (item.children && item.children.length > 0) toggleExpandAll(item.children, status)
  })
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = async (row: DeptVO) => {
    const res = await getDept(row.deptId);
    dialog.visible = true;
    dialog.title = "修改部门";
    nextTick(async () => {
        reset();
        form.value = res.data
        const response = await listDeptExcludeChild(row.deptId);
        const data = proxy?.handleTree<DeptOptionsType>(response.data, "deptId")
        if (data) {
            deptOptions.value = data;
            if (data.length === 0) {
                const noResultsOptions: DeptOptionsType = { deptId: res.data.parentId, deptName: res.data.parentName, children: [] };
                deptOptions.value.push(noResultsOptions);
            }
        }
    })
  const res = await getDept(row.deptId);
  dialog.visible = true;
  dialog.title = "修改部门";
  await nextTick(async () => {
    reset();
    form.value = res.data
    const response = await listDeptExcludeChild(row.deptId);
    const data = proxy?.handleTree<DeptOptionsType>(response.data, "deptId")
    if (data) {
      deptOptions.value = data;
      if (data.length === 0) {
        const noResultsOptions: DeptOptionsType = {
          deptId: res.data.parentId,
          deptName: res.data.parentName,
          children: []
        };
        deptOptions.value.push(noResultsOptions);
      }
    }
  })
}
/** æäº¤æŒ‰é’® */
const submitForm = () => {
    deptFormRef.value.validate(async (valid: boolean) => {
        if (valid) {
            form.value.deptId ? await updateDept(form.value) : await addDept(form.value);
            proxy?.$modal.msgSuccess("操作成功");
            dialog.visible = false;
            getList();
        }
    })
  deptFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      form.value.deptId ? await updateDept(form.value) : await addDept(form.value);
      proxy?.$modal.msgSuccess("操作成功");
      dialog.visible = false;
      await getList();
    }
  })
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row: DeptVO) => {
    await proxy?.$modal.confirm('是否确认删除名称为"' + row.deptName + '"的数据项?');
    await delDept(row.deptId);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
  await proxy?.$modal.confirm('是否确认删除名称为"' + row.deptName + '"的数据项?');
  await delDept(row.deptId);
  await getList();
  proxy?.$modal.msgSuccess("删除成功");
}
onMounted(() => {
    getList();
  getList();
});
</script>
src/views/system/dict/data.vue
@@ -1,29 +1,31 @@
<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="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" />
            </el-select>
          </el-form-item>
          <el-form-item label="字典标签" prop="dictLabel">
            <el-input v-model="queryParams.dictLabel" placeholder="请输入字典标签" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="状态" prop="status">
            <el-select v-model="queryParams.status" placeholder="数据状态" clearable style="width: 200px">
              <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>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      <div class="mb-[10px]" v-show="showSearch">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :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" />
              </el-select>
            </el-form-item>
            <el-form-item label="字典标签" prop="dictLabel">
              <el-input v-model="queryParams.dictLabel" placeholder="请输入字典标签" clearable style="width: 200px" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="状态" prop="status">
              <el-select v-model="queryParams.status" placeholder="数据状态" clearable style="width: 200px">
                <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>
              <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>
      </div>
    </transition>
    <el-card shadow="never">
    <el-card shadow="hover">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
@@ -136,9 +138,7 @@
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 { ComponentInternalInstance } from "vue";
import { DictDataForm, DictDataQuery, DictDataVO } from "@/api/system/dict/data/types";
import { ElForm } from 'element-plus';
const { proxy } = getCurrentInstance() as ComponentInternalInstance
const { sys_normal_disable } = toRefs<any>(proxy?.useDict("sys_normal_disable"));
@@ -154,159 +154,159 @@
const defaultDictType = ref("");
const typeOptions = ref<DictTypeVO[]>([]);
const dataFormRef = ref(ElForm);
const queryFormRef = ref(ElForm);
const dataFormRef = ref<ElFormInstance>();
const queryFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
    visible: false,
    title: ''
  visible: false,
  title: ''
});
// æ•°æ®æ ‡ç­¾å›žæ˜¾æ ·å¼
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: "危险" }
  { value: "default", label: "默认" },
  { value: "primary", label: "主要" },
  { value: "success", label: "成功" },
  { value: "info", label: "信息" },
  { value: "warning", label: "警告" },
  { value: "danger", label: "危险" }
]);
const initFormData: DictDataForm = {
    dictCode: undefined,
    dictLabel: '',
    dictValue: '',
    cssClass: '',
    listClass: "default",
    dictSort: 0,
    status: "0",
    remark: ''
  dictCode: undefined,
  dictLabel: '',
  dictValue: '',
  cssClass: '',
  listClass: "default",
  dictSort: 0,
  status: "0",
  remark: ''
}
const data = reactive<PageData<DictDataForm, DictDataQuery>>({
    form: { ...initFormData },
    queryParams: {
        pageNum: 1,
        pageSize: 10,
        dictName: '',
        dictType: '',
        status: '',
        dictLabel: ''
    },
    rules: {
        dictLabel: [{ required: true, message: "数据标签不能为空", trigger: "blur" }],
        dictValue: [{ required: true, message: "数据键值不能为空", trigger: "blur" }],
        dictSort: [{ required: true, message: "数据顺序不能为空", trigger: "blur" }]
    }
  form: { ...initFormData },
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    dictName: '',
    dictType: '',
    status: '',
    dictLabel: ''
  },
  rules: {
    dictLabel: [{ required: true, message: "数据标签不能为空", trigger: "blur" }],
    dictValue: [{ required: true, message: "数据键值不能为空", trigger: "blur" }],
    dictSort: [{ required: true, message: "数据顺序不能为空", trigger: "blur" }]
  }
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢å­—典类型详细 */
const getTypes = async (dictId: string | number) => {
    const { data } = await getType(dictId);
    queryParams.value.dictType = data.dictType;
    defaultDictType.value = data.dictType;
    getList();
  const { data } = await getType(dictId);
  queryParams.value.dictType = data.dictType;
  defaultDictType.value = data.dictType;
  getList();
}
/** æŸ¥è¯¢å­—典类型列表 */
const getTypeList = async () => {
    const res = await getDictOptionselect()
    typeOptions.value = res.data;
  const res = await getDictOptionselect()
  typeOptions.value = res.data;
}
/** æŸ¥è¯¢å­—典数据列表 */
const getList = async () => {
    loading.value = true;
    const res = await listData(queryParams.value);
    dataList.value = res.rows;
    total.value = res.total;
    loading.value = false;
  loading.value = true;
  const res = await listData(queryParams.value);
  dataList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
/** å–消按钮 */
const cancel = () => {
    dialog.visible = false;
    reset();
  dialog.visible = false;
  reset();
}
/** è¡¨å•重置 */
const reset = () => {
    form.value = { ...initFormData };
    dataFormRef.value.resetFields();
  form.value = { ...initFormData };
  dataFormRef.value?.resetFields();
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    queryParams.value.pageNum = 1;
    getList();
  queryParams.value.pageNum = 1;
  getList();
}
/** è¿”回按钮操作 */
const handleClose = () => {
    const obj = { path: "/system/dict" };
    proxy?.$tab.closeOpenPage(obj);
  const obj = { path: "/system/dict" };
  proxy?.$tab.closeOpenPage(obj);
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
    queryFormRef.value.resetFields();
    queryParams.value.dictType = defaultDictType.value;
    handleQuery();
  queryFormRef.value?.resetFields();
  queryParams.value.dictType = defaultDictType.value;
  handleQuery();
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
    dialog.visible = true;
    dialog.title = "添加字典数据";
    nextTick(() => {
        reset();
        form.value.dictType = queryParams.value.dictType;
    })
  dialog.visible = true;
  dialog.title = "添加字典数据";
  nextTick(() => {
    reset();
    form.value.dictType = queryParams.value.dictType;
  })
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: DictDataVO[]) => {
    ids.value = selection.map(item => item.dictCode);
    single.value = selection.length != 1;
    multiple.value = !selection.length;
  ids.value = selection.map(item => item.dictCode);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = (row?: DictDataVO) => {
    const dictCode = row?.dictCode || ids.value[0];
    dialog.visible = true;
    dialog.title = "修改字典数据";
    nextTick(async () => {
        const res =  await getData(dictCode);
        reset();
        form.value = res.data;
    })
  const dictCode = row?.dictCode || ids.value[0];
  dialog.visible = true;
  dialog.title = "修改字典数据";
  nextTick(async () => {
    const res = await getData(dictCode);
    reset();
    form.value = res.data;
  })
}
/** æäº¤æŒ‰é’® */
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("操作成功");
            dialog.visible = false;
            getList();
  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("操作成功");
      dialog.visible = false;
      await getList();
        }
    });
    }
  });
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: DictDataVO) => {
    const dictCodes = row?.dictCode || ids.value;
    await proxy?.$modal.confirm('是否确认删除字典编码为"' + dictCodes + '"的数据项?');
    await delData(dictCodes);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
    useDictStore().removeDict(queryParams.value.dictType);
  const dictCodes = row?.dictCode || ids.value;
  await proxy?.$modal.confirm('是否确认删除字典编码为"' + dictCodes + '"的数据项?');
  await delData(dictCodes);
  await getList();
  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);
    getTypeList();
  getTypes(route.params && route.params.dictId as string);
  getTypeList();
})
</script>
src/views/system/dict/index.vue
@@ -1,38 +1,40 @@
<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="68px">
          <el-form-item label="字典名称" prop="dictName">
            <el-input v-model="queryParams.dictName" placeholder="请输入字典名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="字典类型" prop="dictType">
            <el-input v-model="queryParams.dictType" 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">
              <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-date-picker
              v-model="dateRange"
              value-format="YYYY-MM-DD HH:mm:ss"
              type="daterange"
              range-separator="-"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
            ></el-date-picker>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      <div class="mb-[10px]" v-show="showSearch">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :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>
            <el-form-item label="字典类型" prop="dictType">
              <el-input v-model="queryParams.dictType" 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">
                <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-date-picker
                v-model="dateRange"
                value-format="YYYY-MM-DD HH:mm:ss"
                type="daterange"
                range-separator="-"
                start-placeholder="开始日期"
                end-placeholder="结束日期"
                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
              ></el-date-picker>
            </el-form-item>
            <el-form-item>
              <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>
      </div>
    </transition>
    <el-card shadow="never">
    <el-card shadow="hover">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
@@ -123,9 +125,7 @@
<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 { ComponentInternalInstance } from "vue";
import { DictTypeForm, DictTypeQuery, DictTypeVO } from "@/api/system/dict/type/types";
import { DateModelType } from 'element-plus';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict("sys_normal_disable"))
@@ -139,128 +139,128 @@
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const dictFormRef = ref(ElForm);
const queryFormRef = ref(ElForm);
const dictFormRef = ref<ElFormInstance>();
const queryFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
    visible: false,
    title: ''
  visible: false,
  title: ''
});
const initFormData: DictTypeForm = {
    dictId: undefined,
    dictName: '',
    dictType: '',
    status: "0",
    remark: ''
  dictId: undefined,
  dictName: '',
  dictType: '',
  status: "0",
  remark: ''
}
const data = reactive<PageData<DictTypeForm, DictTypeQuery>>({
    form: {...initFormData},
    queryParams: {
        pageNum: 1,
        pageSize: 10,
        dictName: '',
        dictType: '',
        status: ''
    },
    rules: {
        dictName: [{ required: true, message: "字典名称不能为空", trigger: "blur" }],
        dictType: [{ required: true, message: "字典类型不能为空", trigger: "blur" }]
    },
  form: { ...initFormData },
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    dictName: '',
    dictType: '',
    status: ''
  },
  rules: {
    dictName: [{ required: true, message: "字典名称不能为空", trigger: "blur" }],
    dictType: [{ required: true, message: "字典类型不能为空", trigger: "blur" }]
  },
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢å­—典类型列表 */
const getList = () => {
    loading.value = true;
    listType(proxy?.addDateRange(queryParams.value, dateRange.value)).then(res => {
        typeList.value = res.rows;
        total.value = res.total;
        loading.value = false;
    });
  loading.value = true;
  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;
  reset();
  dialog.visible = false;
}
/** è¡¨å•重置 */
const reset = () => {
    form.value = {...initFormData};
    dictFormRef.value.resetFields();
  form.value = { ...initFormData };
  dictFormRef.value?.resetFields();
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    queryParams.value.pageNum = 1;
    getList();
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
    dateRange.value = ['', ''];
    queryFormRef.value.resetFields();
    handleQuery();
  dateRange.value = ['', ''];
  queryFormRef.value?.resetFields();
  handleQuery();
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
    dialog.visible = true;
    dialog.title = "添加字典类型";
    nextTick(() => {
        reset();
    })
  dialog.visible = true;
  dialog.title = "添加字典类型";
  nextTick(() => {
    reset();
  })
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: DictTypeVO[]) =>  {
    ids.value = selection.map(item => item.dictId);
    single.value = selection.length != 1;
    multiple.value = !selection.length;
const handleSelectionChange = (selection: DictTypeVO[]) => {
  ids.value = selection.map(item => item.dictId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = (row?: DictTypeVO) => {
    dialog.visible = true;
    dialog.title = "修改字典类型";
    const dictId = row?.dictId || ids.value[0];
    nextTick(async () => {
        reset();
        const res = await getType(dictId);
        form.value = res.data;
    })
  dialog.visible = true;
  dialog.title = "修改字典类型";
  const dictId = row?.dictId || ids.value[0];
  nextTick(async () => {
    reset();
    const res = await getType(dictId);
    form.value = res.data;
  })
}
/** æäº¤æŒ‰é’® */
const submitForm = () => {
    dictFormRef.value.validate(async (valid: boolean) => {
        if (valid) {
            form.value.dictId ? await updateType(form.value) : await addType(form.value);
            proxy?.$modal.msgSuccess("操作成功");
            dialog.visible = false;
            getList();
        }
    });
  dictFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      form.value.dictId ? await updateType(form.value) : await addType(form.value);
      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("删除成功");
  const dictIds = row?.dictId || ids.value;
  await proxy?.$modal.confirm('是否确认删除字典编号为"' + dictIds + '"的数据项?');
  await delType(dictIds);
  getList();
  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("刷新成功");
    useDictStore().cleanDict();
  await refreshCache();
  proxy?.$modal.msgSuccess("刷新成功");
  useDictStore().cleanDict();
}
onMounted(()=>{
    getList();
onMounted(() => {
  getList();
})
</script>
src/views/system/menu/index.vue
@@ -1,25 +1,27 @@
<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 ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
          <el-form-item label="菜单名称" prop="menuName">
            <el-input v-model="queryParams.menuName" placeholder="请输入菜单名称" clearable @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="状态" prop="status">
            <el-select v-model="queryParams.status" placeholder="菜单状态" clearable>
              <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>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      <div class="mb-[10px]" v-show="showSearch">
        <el-card shadow="hover">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
            <el-form-item label="菜单名称" prop="menuName">
              <el-input v-model="queryParams.menuName" placeholder="请输入菜单名称" clearable @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="状态" prop="status">
              <el-select v-model="queryParams.status" placeholder="菜单状态" clearable>
                <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>
              <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>
      </div>
    </transition>
    <el-card shadow="never">
    <el-card shadow="hover">
      <template #header>
        <el-row :gutter="10">
          <el-col :span="1.5">
@@ -37,7 +39,6 @@
        :data="menuList"
        row-key="menuId"
        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
        border
        ref="menuTableRef"
        :default-expand-all="isExpandAll"
      >
@@ -262,14 +263,12 @@
<script setup name="Menu" lang="ts">
import { addMenu, delMenu, getMenu, listMenu, updateMenu } from '@/api/system/menu';
import { MenuForm, MenuQuery, MenuVO } from '@/api/system/menu/types';
import { ComponentInternalInstance } from 'vue';
import { MenuTypeEnum } from '@/enums/MenuTypeEnum';
import { ElTable, ElForm } from 'element-plus';
interface MenuOptionsType {
    menuId: number;
    menuName: string;
    children: MenuOptionsType[] | undefined;
  menuId: number;
  menuName: string;
  children: MenuOptionsType[] | undefined;
}
const { proxy } = getCurrentInstance() as ComponentInternalInstance
@@ -282,136 +281,136 @@
const isExpandAll = ref(false)
const dialog = reactive<DialogOption>({
    visible: false,
    title: ''
  visible: false,
  title: ''
});
const queryFormRef = ref(ElForm);
const menuFormRef = ref(ElForm);
const queryFormRef = ref<ElFormInstance>();
const menuFormRef = ref<ElFormInstance>();
const initFormData = {
    path: '',
    menuId: undefined,
    parentId: 0,
    menuName: '',
    icon: '',
    menuType: MenuTypeEnum.M,
    orderNum: 1,
    isFrame: "1",
    isCache: "0",
    visible: "0",
    status: "0"
  path: '',
  menuId: undefined,
  parentId: 0,
  menuName: '',
  icon: '',
  menuType: MenuTypeEnum.M,
  orderNum: 1,
  isFrame: "1",
  isCache: "0",
  visible: "0",
  status: "0"
}
const data = reactive<PageData<MenuForm, MenuQuery>>({
    form: { ...initFormData },
    queryParams: {
        menuName: undefined,
        status: undefined
    },
    rules: {
        menuName: [{ required: true, message: "菜单名称不能为空", trigger: "blur" }],
        orderNum: [{ required: true, message: "菜单顺序不能为空", trigger: "blur" }],
        path: [{ required: true, message: "路由地址不能为空", trigger: "blur" }]
    },
  form: { ...initFormData },
  queryParams: {
    menuName: undefined,
    status: undefined
  },
  rules: {
    menuName: [{ required: true, message: "菜单名称不能为空", trigger: "blur" }],
    orderNum: [{ required: true, message: "菜单顺序不能为空", trigger: "blur" }],
    path: [{ required: true, message: "路由地址不能为空", trigger: "blur" }]
  },
})
const menuTableRef = ref(ElTable);
const menuTableRef = ref<ElTableInstance>();
const { queryParams, form, rules } = toRefs<PageData<MenuForm, MenuQuery>>(data)
/** æŸ¥è¯¢èœå•列表 */
const getList = async () => {
    loading.value = true
    const res = await listMenu(queryParams.value);
    const data = proxy?.handleTree<MenuVO>(res.data, "menuId")
    if (data) {
        menuList.value = data
    }
    loading.value = false
  loading.value = true
  const res = await listMenu(queryParams.value);
  const data = proxy?.handleTree<MenuVO>(res.data, "menuId")
  if (data) {
    menuList.value = data
  }
  loading.value = false
}
/** æŸ¥è¯¢èœå•下拉树结构 */
const getTreeselect = async () => {
    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)
  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 cancel = () => {
    reset()
    dialog.visible = false
  reset()
  dialog.visible = false
}
/** è¡¨å•重置 */
const reset = () => {
    form.value = { ...initFormData };
    menuFormRef.value.resetFields();
  form.value = { ...initFormData };
  menuFormRef.value?.resetFields();
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    getList();
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
    queryFormRef.value.resetFields();
    handleQuery();
  queryFormRef.value?.resetFields();
  handleQuery();
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = (row?: MenuVO) => {
    dialog.visible = true;
    dialog.title = "添加菜单";
    getTreeselect();
    nextTick(() => {
        reset();
        row && row.menuId ? form.value.parentId = row.menuId : form.value.parentId = 0;
    })
  dialog.visible = true;
  dialog.title = "添加菜单";
  getTreeselect();
  nextTick(() => {
    reset();
    row && row.menuId ? form.value.parentId = row.menuId : form.value.parentId = 0;
  })
}
/** å±•å¼€/折叠操作 */
const handleToggleExpandAll = () => {
    isExpandAll.value = !isExpandAll.value;
    toggleExpandAll(menuList.value, isExpandAll.value)
  isExpandAll.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)
    })
  data.forEach((item: MenuVO) => {
    menuTableRef.value?.toggleRowExpansion(item, status)
    if (item.children && item.children.length > 0) toggleExpandAll(item.children, status)
  })
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = async (row: MenuVO) => {
    await getTreeselect();
    dialog.visible = true;
    dialog.title = "修改菜单";
    await nextTick(async () => {
        if (row.menuId) {
            const { data } = await getMenu(row.menuId);
            reset();
            form.value = data;
        }
    })
  await getTreeselect();
  dialog.visible = true;
  dialog.title = "修改菜单";
  await nextTick(async () => {
    if (row.menuId) {
      const { data } = await getMenu(row.menuId);
      reset();
      form.value = data;
    }
  })
}
/** æäº¤æŒ‰é’® */
const submitForm = () => {
    menuFormRef.value.validate(async (valid: boolean) => {
        if (valid) {
            form.value.menuId ? await updateMenu(form.value) : await addMenu(form.value);
            proxy?.$modal.msgSuccess("操作成功");
            dialog.visible = false;
            getList();
        }
    })
  menuFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      form.value.menuId ? await updateMenu(form.value) : await addMenu(form.value);
      proxy?.$modal.msgSuccess("操作成功");
      dialog.visible = false;
      await getList();
    }
  })
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row: MenuVO) => {
    await proxy?.$modal.confirm('是否确认删除名称为"' + row.menuName + '"的数据项?');
    await delMenu(row.menuId);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
  await proxy?.$modal.confirm('是否确认删除名称为"' + row.menuName + '"的数据项?');
  await delMenu(row.menuId);
  await getList();
  proxy?.$modal.msgSuccess("删除成功");
}
onMounted(() => {
    getList();
  getList();
});
</script>
src/views/system/notice/index.vue
@@ -1,28 +1,30 @@
<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="68px">
          <el-form-item label="公告标题" prop="noticeTitle">
            <el-input v-model="queryParams.noticeTitle" placeholder="请输入公告标题" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="操作人员" prop="createByName">
            <el-input v-model="queryParams.createByName" placeholder="请输入操作人员" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="类型" prop="noticeType">
            <el-select v-model="queryParams.noticeType" placeholder="公告类型" clearable style="width: 200px">
              <el-option v-for="dict in sys_notice_type" :key="dict.value" :label="dict.label" :value="dict.value" />
            </el-select>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      <div class="mb-[10px]" v-show="showSearch">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :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>
            <el-form-item label="操作人员" prop="createByName">
              <el-input v-model="queryParams.createByName" placeholder="请输入操作人员" clearable style="width: 200px" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="类型" prop="noticeType">
              <el-select v-model="queryParams.noticeType" placeholder="公告类型" clearable style="width: 200px">
                <el-option v-for="dict in sys_notice_type" :key="dict.value" :label="dict.label" :value="dict.value" />
              </el-select>
            </el-form-item>
            <el-form-item>
              <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>
      </div>
    </transition>
    <el-card shadow="never">
    <el-card shadow="hover">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
@@ -119,9 +121,7 @@
<script setup name="Notice" lang="ts">
import { listNotice, getNotice, delNotice, addNotice, updateNotice } from "@/api/system/notice";
import { ComponentInternalInstance } from "vue";
import { NoticeForm, NoticeQuery, NoticeVO } from "@/api/system/notice/types";
import { ElForm } from 'element-plus';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_notice_status, sys_notice_type } = toRefs<any>(proxy?.useDict("sys_notice_status", "sys_notice_type"));
@@ -134,116 +134,116 @@
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref(ElForm);
const noticeFormRef = ref(ElForm);
const queryFormRef = ref<ElFormInstance>();
const noticeFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
    visible: false,
    title: ''
  visible: false,
  title: ''
});
const initFormData: NoticeForm = {
    noticeId: undefined,
    noticeTitle: '',
    noticeType: '',
    noticeContent: '',
    status: "0",
    remark: '',
    createByName: ''
  noticeId: undefined,
  noticeTitle: '',
  noticeType: '',
  noticeContent: '',
  status: "0",
  remark: '',
  createByName: ''
}
const data = reactive<PageData<NoticeForm, NoticeQuery>>({
    form: { ...initFormData },
    queryParams: {
        pageNum: 1,
        pageSize: 10,
        noticeTitle: '',
        createByName: '',
        status: '',
        noticeType: ''
    },
    rules: {
        noticeTitle: [{ required: true, message: "公告标题不能为空", trigger: "blur" }],
        noticeType: [{ required: true, message: "公告类型不能为空", trigger: "change" }]
    },
  form: { ...initFormData },
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    noticeTitle: '',
    createByName: '',
    status: '',
    noticeType: ''
  },
  rules: {
    noticeTitle: [{ required: true, message: "公告标题不能为空", trigger: "blur" }],
    noticeType: [{ required: true, message: "公告类型不能为空", trigger: "change" }]
  },
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢å…¬å‘Šåˆ—表 */
const getList = async () => {
    loading.value = true;
    const res = await listNotice(queryParams.value);
    noticeList.value = res.rows;
    total.value = res.total;
    loading.value = false;
  loading.value = true;
  const res = await listNotice(queryParams.value);
  noticeList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
/** å–消按钮 */
const cancel = () => {
    reset();
    dialog.visible = false;
  reset();
  dialog.visible = false;
}
/** è¡¨å•重置 */
const reset = () => {
    form.value = { ...initFormData };
    noticeFormRef.value.resetFields();
  form.value = { ...initFormData };
  noticeFormRef.value?.resetFields();
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    queryParams.value.pageNum = 1;
    getList();
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
    queryFormRef.value.resetFields();
    handleQuery();
  queryFormRef.value?.resetFields();
  handleQuery();
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: NoticeVO[]) => {
    ids.value = selection.map(item => item.noticeId);
    single.value = selection.length != 1;
    multiple.value = !selection.length;
  ids.value = selection.map(item => item.noticeId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
    dialog.visible = true;
    dialog.title = "添加公告";
    nextTick(() => {
        reset();
    })
  dialog.visible = true;
  dialog.title = "添加公告";
  nextTick(() => {
    reset();
  })
}
/**修改按钮操作 */
const handleUpdate = (row?: NoticeVO) => {
    dialog.visible = true;
    dialog.title = "修改公告";
    nextTick(async () => {
        const noticeId = row?.noticeId || ids.value[0];
        reset();
        const { data } = await getNotice(noticeId);
        form.value = data;
    })
  dialog.visible = true;
  dialog.title = "修改公告";
  nextTick(async () => {
    const noticeId = row?.noticeId || ids.value[0];
    reset();
    const { data } = await getNotice(noticeId);
    form.value = data;
  })
}
/** æäº¤æŒ‰é’® */
const submitForm = () => {
    noticeFormRef.value.validate(async (valid: boolean) => {
        if (valid) {
            form.value.noticeId ? await updateNotice(form.value) : await addNotice(form.value);
            proxy?.$modal.msgSuccess("修改成功");
            dialog.visible = false;
            getList();
        }
    });
  noticeFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      form.value.noticeId ? await updateNotice(form.value) : await addNotice(form.value);
      proxy?.$modal.msgSuccess("修改成功");
      dialog.visible = false;
      await getList();
    }
  });
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: NoticeVO) => {
    const noticeIds = row?.noticeId || ids.value
    await proxy?.$modal.confirm('是否确认删除公告编号为"' + noticeIds + '"的数据项?');
    await delNotice(noticeIds);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
  const noticeIds = row?.noticeId || ids.value
  await proxy?.$modal.confirm('是否确认删除公告编号为"' + noticeIds + '"的数据项?');
  await delNotice(noticeIds);
  await getList();
  proxy?.$modal.msgSuccess("删除成功");
}
onMounted(() => {
    getList();
  getList();
})
</script>
</script>
src/views/system/oss/config.vue
@@ -1,29 +1,31 @@
<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="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>
          <el-form-item label="桶名称" prop="bucketName">
            <el-input v-model="queryParams.bucketName" placeholder="请输入桶名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="是否默认" prop="status">
            <el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 200px">
              <el-option key="0" label="是" value="0" />
              <el-option key="1" label="否" value="1" />
            </el-select>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      <div class="mb-[10px]" v-show="showSearch">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :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>
            <el-form-item label="桶名称" prop="bucketName">
              <el-input v-model="queryParams.bucketName" placeholder="请输入桶名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="是否默认" prop="status">
              <el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width: 200px">
                <el-option key="0" label="是" value="0" />
                <el-option key="1" label="否" value="1" />
              </el-select>
            </el-form-item>
            <el-form-item>
              <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>
      </div>
    </transition>
    <el-card shadow="never">
    <el-card shadow="hover">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
@@ -131,16 +133,14 @@
<script setup name="OssConfig" lang="ts">
import {
    listOssConfig,
    getOssConfig,
    delOssConfig,
    addOssConfig,
    updateOssConfig,
    changeOssConfigStatus
  listOssConfig,
  getOssConfig,
  delOssConfig,
  addOssConfig,
  updateOssConfig,
  changeOssConfigStatus
} from "@/api/system/ossConfig";
import { ComponentInternalInstance } from "vue";
import { OssConfigForm, OssConfigQuery, OssConfigVO } from "@/api/system/ossConfig/types";
import { ElForm } from 'element-plus';
const { proxy } = getCurrentInstance() as ComponentInternalInstance
@@ -155,193 +155,193 @@
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref(ElForm);
const ossConfigFormRef = ref(ElForm);
const queryFormRef = ref<ElFormInstance>();
const ossConfigFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
    visible: false,
    title: ''
  visible: false,
  title: ''
});
// åˆ—显隐信息
const columns = ref<FieldOption[]>([
    { key: 0, label: `主建`, visible: true },
    { key: 1, label: `配置key`, visible: false },
    { key: 2, label: `访问站点`, visible: true },
    { key: 3, label: `自定义域名`, visible: true },
    { key: 4, label: `桶名称`, visible: true },
    { key: 5, label: `前缀`, visible: true },
    { key: 6, label: `域`, visible: true },
    { key: 7, label: `桶权限类型`, visible: true },
    { key: 8, label: `状态`, visible: true }
  { key: 0, label: `主建`, visible: true },
  { key: 1, label: `配置key`, visible: false },
  { key: 2, label: `访问站点`, visible: true },
  { key: 3, label: `自定义域名`, visible: true },
  { key: 4, label: `桶名称`, visible: true },
  { key: 5, label: `前缀`, visible: true },
  { key: 6, label: `域`, visible: true },
  { key: 7, label: `桶权限类型`, visible: true },
  { key: 8, label: `状态`, visible: true }
]);
const initFormData: OssConfigForm = {
    ossConfigId: undefined,
    configKey: '',
    accessKey: '',
    secretKey: '',
    bucketName: '',
    prefix: '',
    endpoint: '',
    domain: '',
    isHttps: "N",
    accessPolicy: "1",
    region: '',
    status: "1",
    remark: '',
  ossConfigId: undefined,
  configKey: '',
  accessKey: '',
  secretKey: '',
  bucketName: '',
  prefix: '',
  endpoint: '',
  domain: '',
  isHttps: "N",
  accessPolicy: "1",
  region: '',
  status: "1",
  remark: '',
}
const data = reactive<PageData<OssConfigForm, OssConfigQuery>>({
    form: { ...initFormData },
    // æŸ¥è¯¢å‚æ•°
    queryParams: {
        pageNum: 1,
        pageSize: 10,
        configKey: '',
        bucketName: '',
        status: '',
    },
    rules: {
        configKey: [{ required: true, message: "configKey不能为空", trigger: "blur" },],
        accessKey: [
            { required: true, message: "accessKey不能为空", trigger: "blur" },
            {
                min: 2,
                max: 200,
                message: "accessKey长度必须介于 2 å’Œ 100 ä¹‹é—´",
                trigger: "blur",
            },
        ],
        secretKey: [
            { required: true, message: "secretKey不能为空", trigger: "blur" },
            {
                min: 2,
                max: 100,
                message: "secretKey长度必须介于 2 å’Œ 100 ä¹‹é—´",
                trigger: "blur",
            },
        ],
        bucketName: [
            { required: true, message: "bucketName不能为空", trigger: "blur" },
            {
                min: 2,
                max: 100,
                message: "bucketName长度必须介于 2 å’Œ 100 ä¹‹é—´",
                trigger: "blur",
            },
        ],
        endpoint: [
            { required: true, message: "endpoint不能为空", trigger: "blur" },
            {
                min: 2,
                max: 100,
                message: "endpoint名称长度必须介于 2 å’Œ 100 ä¹‹é—´",
                trigger: "blur",
            },
        ],
        accessPolicy: [{ required: true, message: "accessPolicy不能为空", trigger: "blur" }]
    }
  form: { ...initFormData },
  // æŸ¥è¯¢å‚æ•°
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    configKey: '',
    bucketName: '',
    status: '',
  },
  rules: {
    configKey: [{ required: true, message: "configKey不能为空", trigger: "blur" },],
    accessKey: [
      { required: true, message: "accessKey不能为空", trigger: "blur" },
      {
        min: 2,
        max: 200,
        message: "accessKey长度必须介于 2 å’Œ 100 ä¹‹é—´",
        trigger: "blur",
      },
    ],
    secretKey: [
      { required: true, message: "secretKey不能为空", trigger: "blur" },
      {
        min: 2,
        max: 100,
        message: "secretKey长度必须介于 2 å’Œ 100 ä¹‹é—´",
        trigger: "blur",
      },
    ],
    bucketName: [
      { required: true, message: "bucketName不能为空", trigger: "blur" },
      {
        min: 2,
        max: 100,
        message: "bucketName长度必须介于 2 å’Œ 100 ä¹‹é—´",
        trigger: "blur",
      },
    ],
    endpoint: [
      { required: true, message: "endpoint不能为空", trigger: "blur" },
      {
        min: 2,
        max: 100,
        message: "endpoint名称长度必须介于 2 å’Œ 100 ä¹‹é—´",
        trigger: "blur",
      },
    ],
    accessPolicy: [{ required: true, message: "accessPolicy不能为空", trigger: "blur" }]
  }
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢å¯¹è±¡å­˜å‚¨é…ç½®åˆ—表 */
const getList = async () => {
    loading.value = true;
    const res = await listOssConfig(queryParams.value);
    ossConfigList.value = res.rows;
    total.value = res.total;
    loading.value = false;
  loading.value = true;
  const res = await listOssConfig(queryParams.value);
  ossConfigList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
/** å–消按钮 */
const cancel = () => {
    dialog.visible = false;
    reset();
  dialog.visible = false;
  reset();
}
/** è¡¨å•重置 */
const reset = () => {
    form.value = { ...initFormData };
    ossConfigFormRef.value.resetFields();
  form.value = { ...initFormData };
  ossConfigFormRef.value?.resetFields();
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    queryParams.value.pageNum = 1;
    getList();
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
    queryFormRef.value.resetFields();
    handleQuery();
  queryFormRef.value?.resetFields();
  handleQuery();
}
/** é€‰æ‹©æ¡æ•°  */
const handleSelectionChange = (selection: OssConfigVO[]) => {
    ids.value = selection.map(item => item.ossConfigId);
    single.value = selection.length != 1;
    multiple.value = !selection.length;
  ids.value = selection.map(item => item.ossConfigId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
    dialog.visible = true;
    dialog.title = "添加对象存储配置";
    nextTick(() => {
        reset();
    })
  dialog.visible = true;
  dialog.title = "添加对象存储配置";
  nextTick(() => {
    reset();
  })
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = (row?: OssConfigVO) => {
    loading.value = true;
    dialog.visible = true;
    dialog.title = "修改对象存储配置";
    const ossConfigId = row?.ossConfigId || ids.value[0];
    nextTick(async () => {
        reset();
        const res = await getOssConfig(ossConfigId);
        loading.value = false;
        form.value = res.data;
    })
  loading.value = true;
  dialog.visible = true;
  dialog.title = "修改对象存储配置";
  const ossConfigId = row?.ossConfigId || ids.value[0];
  nextTick(async () => {
    reset();
    const res = await getOssConfig(ossConfigId);
    loading.value = false;
    form.value = res.data;
  })
}
/** æäº¤æŒ‰é’® */
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);
            } else {
                await addOssConfig(form.value).finally(() => buttonLoading.value = false);
            }
            proxy?.$modal.msgSuccess("新增成功");
            dialog.visible = false;
            getList();
        }
    });
  ossConfigFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      buttonLoading.value = true;
      if (form.value.ossConfigId) {
        await updateOssConfig(form.value).finally(() => buttonLoading.value = false);
      } else {
        await addOssConfig(form.value).finally(() => buttonLoading.value = false);
      }
      proxy?.$modal.msgSuccess("新增成功");
      dialog.visible = false;
      getList();
    }
  });
}
/** çŠ¶æ€ä¿®æ”¹  */
const  handleStatusChange = async (row: OssConfigVO) => {
    let text = row.status === "0" ? "启用" : "停用";
    try {
        await proxy?.$modal.confirm('确认要"' + text + '""' + row.configKey + '"配置吗?');
        await changeOssConfigStatus(row.ossConfigId, row.status, row.configKey);
        getList()
        proxy?.$modal.msgSuccess(text + "成功");
    } catch { return } finally {
        row.status = row.status === "0" ? "1" : "0";
    }
const handleStatusChange = async (row: OssConfigVO) => {
  let text = row.status === "0" ? "启用" : "停用";
  try {
    await proxy?.$modal.confirm('确认要"' + text + '""' + row.configKey + '"配置吗?');
    await changeOssConfigStatus(row.ossConfigId, row.status, row.configKey);
    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);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
  const ossConfigIds = row?.ossConfigId || ids.value;
  await proxy?.$modal.confirm('是否确认删除OSS配置编号为"' + ossConfigIds + '"的数据项?');
  loading.value = true;
  await delOssConfig(ossConfigIds).finally(() => loading.value = false);
  getList();
  proxy?.$modal.msgSuccess("删除成功");
}
onMounted(() => {
    getList();
  getList();
})
</script>
</script>
src/views/system/oss/index.vue
@@ -1,40 +1,42 @@
<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="68px">
          <el-form-item label="文件名" prop="fileName">
            <el-input v-model="queryParams.fileName" placeholder="请输入文件名" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="原名" prop="originalName">
            <el-input v-model="queryParams.originalName" placeholder="请输入原名" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="文件后缀" prop="fileSuffix">
            <el-input v-model="queryParams.fileSuffix" placeholder="请输入文件后缀" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="创建时间">
            <el-date-picker
              v-model="daterangeCreateTime"
              value-format="YYYY-MM-DD HH:mm:ss"
              type="daterange"
              range-separator="-"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
            ></el-date-picker>
          </el-form-item>
          <el-form-item label="服务商" prop="service">
            <el-input v-model="queryParams.service" placeholder="请输入服务商" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item>
            <el-button type="primary" icon="search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      <div class="mb-[10px]" v-show="showSearch">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :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>
            <el-form-item label="原名" prop="originalName">
              <el-input v-model="queryParams.originalName" placeholder="请输入原名" clearable style="width: 200px" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="文件后缀" prop="fileSuffix">
              <el-input v-model="queryParams.fileSuffix" placeholder="请输入文件后缀" clearable style="width: 200px" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="创建时间">
              <el-date-picker
                v-model="daterangeCreateTime"
                value-format="YYYY-MM-DD HH:mm:ss"
                type="daterange"
                range-separator="-"
                start-placeholder="开始日期"
                end-placeholder="结束日期"
                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
              ></el-date-picker>
            </el-form-item>
            <el-form-item label="服务商" prop="service">
              <el-input v-model="queryParams.service" placeholder="请输入服务商" clearable style="width: 200px" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item>
              <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>
      </div>
    </transition>
    <el-card shadow="never">
    <el-card shadow="hover">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
@@ -133,9 +135,7 @@
<script setup name="Oss" lang="ts">
import { listOss, delOss } from "@/api/system/oss";
import ImagePreview from "@/components/ImagePreview/index.vue";
import { ComponentInternalInstance } from "vue";
import { OssForm, OssQuery, OssVO } from "@/api/system/oss/types";
import { DateModelType } from 'element-plus';
const router = useRouter();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -154,186 +154,186 @@
const daterangeCreateTime = ref<[DateModelType, DateModelType]>(['', '']);
const dialog = reactive<DialogOption>({
    visible: false,
    title: ''
  visible: false,
  title: ''
});
// é»˜è®¤æŽ’序
const defaultSort = ref({ prop: 'createTime', order: 'ascending' });
const ossFormRef = ref(ElForm);
const queryFormRef = ref(ElForm);
const ossFormRef = ref<ElFormInstance>();
const queryFormRef = ref<ElFormInstance>();
const initFormData = {
    file: undefined,
  file: undefined,
}
const data = reactive<PageData<OssForm, OssQuery>>({
    form: { ...initFormData },
    // æŸ¥è¯¢å‚æ•°
    queryParams: {
        pageNum: 1,
        pageSize: 10,
        fileName: '',
        originalName: '',
        fileSuffix: '',
        createTime: '',
        service: '',
        orderByColumn: defaultSort.value.prop,
        isAsc: defaultSort.value.order
    },
    rules: {
        file: [
            { required: true, message: "文件不能为空", trigger: "blur" }
        ]
    }
  form: { ...initFormData },
  // æŸ¥è¯¢å‚æ•°
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    fileName: '',
    originalName: '',
    fileSuffix: '',
    createTime: '',
    service: '',
    orderByColumn: defaultSort.value.prop,
    isAsc: defaultSort.value.order
  },
  rules: {
    file: [
      { required: true, message: "文件不能为空", trigger: "blur" }
    ]
  }
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢OSS对象存储列表 */
const getList = async () => {
    loading.value = true;
    const res = await proxy?.getConfigKey("sys.oss.previewListResource");
    previewListResource.value = res?.msg === undefined ? true : res.msg === 'true';
    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;
  loading.value = true;
  const res = await proxy?.getConfigKey("sys.oss.previewListResource");
  previewListResource.value = res?.msg === undefined ? true : res.msg === 'true';
  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 => {
        return fileSuffix.indexOf(type) > -1;
    });
  let arr = ["png", "jpg", "jpeg"];
  return arr.some(type => {
    return fileSuffix.indexOf(type) > -1;
  });
}
/** å–消按钮 */
function cancel() {
    dialog.visible = false;
    reset();
  dialog.visible = false;
  reset();
}
/** è¡¨å•重置 */
function reset() {
    form.value = { ...initFormData };
    ossFormRef.value.resetFields();
  form.value = { ...initFormData };
  ossFormRef.value?.resetFields();
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
function handleQuery() {
    queryParams.value.pageNum = 1;
    getList();
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
function resetQuery() {
    showTable.value = false;
    daterangeCreateTime.value = ['', ''];
    queryFormRef.value.resetFields();
    queryParams.value.orderByColumn = defaultSort.value.prop;
    queryParams.value.isAsc = defaultSort.value.order;
    handleQuery();
  showTable.value = false;
  daterangeCreateTime.value = ['', ''];
  queryFormRef.value?.resetFields();
  queryParams.value.orderByColumn = defaultSort.value.prop;
  queryParams.value.isAsc = defaultSort.value.order;
  handleQuery();
}
/** é€‰æ‹©æ¡æ•°  */
function handleSelectionChange(selection: OssVO[]) {
    ids.value = selection.map(item => item.ossId);
    single.value = selection.length != 1;
    multiple.value = !selection.length;
  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
    }
    switch (column.multiOrder) {
        case 'descending':
            column.multiOrder = 'ascending';
            break;
        case 'ascending':
            column.multiOrder = '';
            break;
        default:
            column.multiOrder = 'descending';
            break;
    }
    handleOrderChange(column.property, column.multiOrder)
  if (column.sortable !== 'custom') {
    return
  }
  switch (column.multiOrder) {
    case 'descending':
      column.multiOrder = 'ascending';
      break;
    case 'ascending':
      column.multiOrder = '';
      break;
    default:
      column.multiOrder = 'descending';
      break;
  }
  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)
    if (propIndex !== -1) {
        if (order) {
            //排序里已存在 åªä¿®æ”¹æŽ’序
            isAscArr[propIndex] = order;
        } else {
            //如果order为null åˆ™åˆ é™¤æŽ’序字段和属性
            isAscArr.splice(propIndex, 1);//删除排序
            orderByArr.splice(propIndex, 1);//删除属性
        }
  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 {
        //排序里不存在则新增排序
        orderByArr.push(prop);
        isAscArr.push(order);
      //如果order为null åˆ™åˆ é™¤æŽ’序字段和属性
      isAscArr.splice(propIndex, 1);//删除排序
      orderByArr.splice(propIndex, 1);//删除属性
    }
    //合并排序
    queryParams.value.orderByColumn = orderByArr.join(",");
    queryParams.value.isAsc = isAscArr.join(",");
    getList();
  } else {
    //排序里不存在则新增排序
    orderByArr.push(prop);
    isAscArr.push(order);
  }
  //合并排序
  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 = () => {
    dialog.visible = true;
    dialog.title = "上传文件";
    nextTick(() => {
        reset();
        type.value = 0;
    })
  dialog.visible = true;
  dialog.title = "上传文件";
  nextTick(() => {
    reset();
    type.value = 0;
  })
}
/** å›¾ç‰‡æŒ‰é’®æ“ä½œ */
const handleImage = () => {
    dialog.visible = true;
    dialog.title = "上传图片";
    nextTick(() => {
        reset();
        type.value = 1;
    })
  dialog.visible = true;
  dialog.title = "上传图片";
  nextTick(() => {
    reset();
    type.value = 1;
  })
}
/** æäº¤æŒ‰é’® */
const submitForm = () => {
    dialog.visible = false;
    getList();
  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 ? "启用" : "停用";
    try {
      await proxy?.$modal.confirm('确认要"' + text + '""预览列表图片"配置吗?');
      await proxy?.updateConfigByKey("sys.oss.previewListResource", preview);
      getList()
      proxy?.$modal.msgSuccess(text + "成功");
    } catch { return }
  let text = preview ? "启用" : "停用";
  try {
    await proxy?.$modal.confirm('确认要"' + text + '""预览列表图片"配置吗?');
    await proxy?.updateConfigByKey("sys.oss.previewListResource", preview);
    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);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
  const ossIds = row?.ossId || ids.value;
  await proxy?.$modal.confirm('是否确认删除OSS对象存储编号为"' + ossIds + '"的数据项?');
  loading.value = true;
  await delOss(ossIds).finally(() => loading.value = false);
  getList();
  proxy?.$modal.msgSuccess("删除成功");
}
onMounted(() => {
    getList();
  getList();
})
</script>
src/views/system/post/index.vue
@@ -1,27 +1,29 @@
<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="70">
          <el-form-item label="岗位编码" prop="postCode">
            <el-input v-model="queryParams.postCode" placeholder="请输入岗位编码" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="岗位名称" prop="postName">
            <el-input v-model="queryParams.postName" placeholder="请输入岗位名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="状态" prop="status">
            <el-select v-model="queryParams.status" placeholder="岗位状态" clearable style="width: 200px">
              <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>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      <div class="mb-[10px]" v-show="showSearch">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :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>
            <el-form-item label="岗位名称" prop="postName">
              <el-input v-model="queryParams.postName" placeholder="请输入岗位名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="状态" prop="status">
              <el-select v-model="queryParams.status" placeholder="岗位状态" clearable style="width: 200px">
                <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>
              <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>
      </div>
    </transition>
    <el-card shadow="never">
    <el-card shadow="hover">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
@@ -107,7 +109,6 @@
<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 { ComponentInternalInstance } from "vue";
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable } = toRefs<any>(proxy?.useDict("sys_normal_disable"));
@@ -120,121 +121,121 @@
const multiple = ref(true);
const total = ref(0);
const postFormRef = ref(ElForm);
const queryFormRef = ref(ElForm);
const postFormRef = ref<ElFormInstance>();
const queryFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
    visible: false,
    title: ''
  visible: false,
  title: ''
});
const initFormData: PostForm = {
    postId: undefined,
    postCode: '',
    postName: '',
    postSort: 0,
    status: "0",
    remark: ''
  postId: undefined,
  postCode: '',
  postName: '',
  postSort: 0,
  status: "0",
  remark: ''
}
const data = reactive<PageData<PostForm, PostQuery>>({
    form: {...initFormData},
    queryParams: {
        pageNum: 1,
        pageSize: 10,
        postCode: '',
        postName: '',
        status: ''
    },
    rules: {
        postName: [{ required: true, message: "岗位名称不能为空", trigger: "blur" }],
        postCode: [{ required: true, message: "岗位编码不能为空", trigger: "blur" }],
        postSort: [{ required: true, message: "岗位顺序不能为空", trigger: "blur" }],
    }
  form: { ...initFormData },
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    postCode: '',
    postName: '',
    status: ''
  },
  rules: {
    postName: [{ required: true, message: "岗位名称不能为空", trigger: "blur" }],
    postCode: [{ required: true, message: "岗位编码不能为空", trigger: "blur" }],
    postSort: [{ required: true, message: "岗位顺序不能为空", trigger: "blur" }],
  }
});
const { queryParams, form, rules } = toRefs<PageData<PostForm, PostQuery>>(data);
/** æŸ¥è¯¢å²—位列表 */
const getList = async () => {
    loading.value = true;
    const res = await listPost(queryParams.value);
    postList.value = res.rows;
    total.value = res.total;
    loading.value = false;
  loading.value = true;
  const res = await listPost(queryParams.value);
  postList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
/** å–消按钮 */
const cancel = () => {
    reset();
    dialog.visible = false;
  reset();
  dialog.visible = false;
}
/** è¡¨å•重置 */
const reset = () => {
    form.value = {...initFormData};
    postFormRef.value.resetFields();
  form.value = { ...initFormData };
  postFormRef.value?.resetFields();
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    queryParams.value.pageNum = 1;
    getList();
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
    queryFormRef.value.resetFields();
    handleQuery();
  queryFormRef.value?.resetFields();
  handleQuery();
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: PostVO[]) => {
    ids.value = selection.map(item => item.postId);
    single.value = selection.length != 1;
    multiple.value = !selection.length;
  ids.value = selection.map(item => item.postId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
    dialog.visible = true;
    dialog.title = "添加岗位";
    nextTick(() => {
        reset();
    })
  dialog.visible = true;
  dialog.title = "添加岗位";
  nextTick(() => {
    reset();
  })
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = (row?: PostVO) => {
    dialog.visible = true;
    dialog.title = "修改岗位";
    nextTick(async () => {
        reset();
        const postId = row?.postId || ids.value[0];
        const res = await getPost(postId);
        form.value = res.data;
    })
  dialog.visible = true;
  dialog.title = "修改岗位";
  nextTick(async () => {
    reset();
    const postId = row?.postId || ids.value[0];
    const res = await getPost(postId);
    form.value = res.data;
  })
}
/** æäº¤æŒ‰é’® */
const submitForm = () => {
    postFormRef.value.validate(async (valid: boolean) => {
        if (valid) {
            form.value.postId ? await updatePost(form.value) : await addPost(form.value);
            proxy?.$modal.msgSuccess("操作成功");
            dialog.visible = false;
            getList();
        }
    });
  postFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      form.value.postId ? await updatePost(form.value) : await addPost(form.value);
      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);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
  const postIds = row?.postId || ids.value;
  await proxy?.$modal.confirm('是否确认删除岗位编号为"' + postIds + '"的数据项?');
  await delPost(postIds);
  await getList();
  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();
  getList();
});
</script>
src/views/system/role/authUser.vue
@@ -58,13 +58,7 @@
        </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" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
      <select-user ref="selectRef" :roleId="queryParams.roleId" @ok="handleQuery" />
    </el-card>
  </div>
@@ -73,10 +67,8 @@
<script setup name="AuthUser" lang="ts">
import { allocatedUserList, authUserCancel, authUserCancelAll } from "@/api/system/role";
import { UserQuery } from "@/api/system/user/types";
import { ComponentInternalInstance } from "vue";
import { UserVO } from "@/api/system/user/types";
import SelectUser from "./selectUser.vue";
// import { ElForm, ElSelect} from 'element-plus';
const route = useRoute();
@@ -90,68 +82,68 @@
const total = ref(0);
const userIds = ref<Array<string | number>>([]);
const queryFormRef = ref(ElForm);
const selectRef = ref(SelectUser);
const queryFormRef = ref<ElFormInstance>();
const selectRef = ref<InstanceType<typeof SelectUser>>();
const queryParams = reactive<UserQuery>({
    pageNum: 1,
    pageSize: 10,
    roleId: route.params.roleId as string,
    userName: undefined,
    phonenumber: undefined,
  pageNum: 1,
  pageSize: 10,
  roleId: route.params.roleId as string,
  userName: undefined,
  phonenumber: undefined,
});
/** æŸ¥è¯¢æŽˆæƒç”¨æˆ·åˆ—表 */
const getList = async () => {
    loading.value = true;
    const res = await allocatedUserList(queryParams);
    userList.value = res.rows;
    total.value = res.total;
    loading.value = false;
  loading.value = true;
  const res = await allocatedUserList(queryParams);
  userList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
// è¿”回按钮
const handleClose = () => {
    const obj = { path: "/system/role" };
    proxy?.$tab.closeOpenPage(obj);
  const obj = { path: "/system/role" };
  proxy?.$tab.closeOpenPage(obj);
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery=() => {
    queryParams.pageNum = 1;
    getList();
const handleQuery = () => {
  queryParams.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery=() =>{
    queryFormRef.value.resetFields();
    handleQuery();
const resetQuery = () => {
  queryFormRef.value?.resetFields();
  handleQuery();
}
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
const handleSelectionChange = (selection: UserVO[]) =>{
    userIds.value = selection.map(item => item.userId);
    multiple.value = !selection.length;
const handleSelectionChange = (selection: UserVO[]) => {
  userIds.value = selection.map(item => item.userId);
  multiple.value = !selection.length;
}
/** æ‰“开授权用户表弹窗 */
const openSelectUser = () => {
    selectRef.value.show();
  selectRef.value?.show();
}
/** å–消授权按钮操作 */
const cancelAuthUser = async (row: UserVO) => {
    await proxy?.$modal.confirm('确认要取消该用户"' + row.userName + '"角色吗?');
    await authUserCancel({ userId: row.userId, roleId: queryParams.roleId });
    getList();
    proxy?.$modal.msgSuccess("取消授权成功");
  await proxy?.$modal.confirm('确认要取消该用户"' + row.userName + '"角色吗?');
  await authUserCancel({ userId: row.userId, roleId: queryParams.roleId });
  await getList();
  proxy?.$modal.msgSuccess("取消授权成功");
}
/** æ‰¹é‡å–消授权按钮操作 */
const cancelAuthUserAll = async () => {
    const roleId = queryParams.roleId;
    const uIds = userIds.value.join(",");
    await proxy?.$modal.confirm("是否取消选中用户授权数据项?");
    await authUserCancelAll({ roleId: roleId, userIds: uIds });
    getList();
    proxy?.$modal.msgSuccess("取消授权成功");
  const roleId = queryParams.roleId;
  const uIds = userIds.value.join(",");
  await proxy?.$modal.confirm("是否取消选中用户授权数据项?");
  await authUserCancelAll({ roleId: roleId, userIds: uIds });
  await getList();
  proxy?.$modal.msgSuccess("取消授权成功");
}
onMounted(() => {
    getList();
  getList();
});
</script>
src/views/system/role/index.vue
@@ -1,40 +1,42 @@
<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 ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
          <el-form-item label="角色名称" prop="roleName">
            <el-input v-model="queryParams.roleName" placeholder="请输入角色名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="权限字符" prop="roleKey">
            <el-input v-model="queryParams.roleKey" 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">
              <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-date-picker
              v-model="dateRange"
              value-format="YYYY-MM-DD"
              type="daterange"
              range-separator="-"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
            ></el-date-picker>
          </el-form-item>
      <div class="mb-[10px]" v-show="showSearch">
        <el-card shadow="hover">
          <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
            <el-form-item label="角色名称" prop="roleName">
              <el-input v-model="queryParams.roleName" placeholder="请输入角色名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="权限字符" prop="roleKey">
              <el-input v-model="queryParams.roleKey" 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">
                <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-date-picker
                v-model="dateRange"
                value-format="YYYY-MM-DD"
                type="daterange"
                range-separator="-"
                start-placeholder="开始日期"
                end-placeholder="结束日期"
                :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
              ></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-form-item>
        </el-form>
            <el-form-item>
              <el-button type="primary" @click="handleQuery" icon="Search">搜索</el-button>
              <el-button @click="resetQuery" icon="Refresh">重置</el-button>
            </el-form-item>
          </el-form>
        </el-card>
      </div>
    </transition>
    <el-card shadow="never">
    <el-card shadow="hover">
      <template #header>
        <el-row :gutter="10">
          <el-col :span="1.5">
@@ -119,7 +121,7 @@
        <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
              dict.label
            }}</el-radio>
          </el-radio-group>
        </el-form-item>
@@ -196,8 +198,6 @@
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';
import { ComponentInternalInstance } from 'vue';
import { ElTree, ElForm, DateModelType } from 'element-plus';
const router = useRouter();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -221,54 +221,54 @@
/** æ•°æ®èŒƒå›´é€‰é¡¹*/
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(ElForm);
const roleFormRef = ref(ElForm);
const dataScopeRef = ref(ElForm);
const menuRef = ref(ElTree);
const deptRef = ref(ElTree);
const queryFormRef = ref<ElFormInstance>();
const roleFormRef = ref<ElFormInstance>();
const dataScopeRef = ref<ElFormInstance>();
const menuRef = ref<ElTreeInstance>();
const deptRef = ref<ElTreeInstance>();
const initForm: RoleForm = {
    roleId: undefined,
    roleSort: 1,
    status: '0',
    roleName: '',
    roleKey: '',
    menuCheckStrictly: true,
    deptCheckStrictly: true,
    remark: '',
    dataScope: '1',
    menuIds: [],
    deptIds: [],
  roleId: undefined,
  roleSort: 1,
  status: '0',
  roleName: '',
  roleKey: '',
  menuCheckStrictly: true,
  deptCheckStrictly: true,
  remark: '',
  dataScope: '1',
  menuIds: [],
  deptIds: [],
}
const data = reactive<PageData<RoleForm, RoleQuery>>({
    form: {...initForm},
    queryParams: {
        pageNum: 1,
        pageSize: 10,
        roleName: '',
        roleKey: '',
        status: '',
    },
    rules: {
        roleName: [{ required: true, message: "角色名称不能为空", trigger: "blur" }],
        roleKey: [{ required: true, message: "权限字符不能为空", trigger: "blur" }],
        roleSort: [{ required: true, message: "角色顺序不能为空", trigger: "blur" }]
    }
  form: { ...initForm },
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    roleName: '',
    roleKey: '',
    status: '',
  },
  rules: {
    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 dialog = reactive<DialogOption>({
    visible: false,
    title: ''
  visible: false,
  title: ''
});
@@ -276,233 +276,241 @@
 * æŸ¥è¯¢è§’色列表
 */
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
  })
}
/**
 * æœç´¢æŒ‰é’®æ“ä½œ
 */
const handleQuery = () => {
    queryParams.value.pageNum = 1;
    getList();
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½® */
const resetQuery = () => {
    dateRange.value = ['', '']
    queryFormRef.value.resetFields();
    handleQuery();
  dateRange.value = ['', '']
  queryFormRef.value?.resetFields();
  handleQuery();
}
/**删除按钮操作 */
const handleDelete = async (row?: RoleVO) => {
    const roleids = row?.roleId || ids.value;
    await proxy?.$modal.confirm('是否确认删除角色编号为' + roleids + '数据项目');
    await delRole(roleids);
    getList();
    proxy?.$modal.msgSuccess('删除成功');
  const roleids = row?.roleId || ids.value;
  await proxy?.$modal.confirm('是否确认删除角色编号为' + roleids + '数据项目');
  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;
  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" ? "启用" : "停用";
    try {
        await proxy?.$modal.confirm('确认要"' + text + '""' + row.roleName + '"角色吗?');
        await changeRoleStatus(row.roleId, row.status);
        proxy?.$modal.msgSuccess(text + "成功");
    } catch {
        row.status = row.status === "0" ? "1" : "0";
    }
  let text = row.status === "0" ? "启用" : "停用";
  try {
    await proxy?.$modal.confirm('确认要"' + text + '""' + row.roleName + '"角色吗?');
    await changeRoleStatus(row.roleId, row.status);
    proxy?.$modal.msgSuccess(text + "成功");
  } catch {
    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 res = await menuTreeselect();
  menuOptions.value = res.data;
}
/** æ‰€æœ‰éƒ¨é—¨èŠ‚ç‚¹æ•°æ® */
const getDeptAllCheckedKeys = () => {
    // ç›®å‰è¢«é€‰ä¸­çš„部门节点
    let checkedKeys = deptRef.value.getCheckedKeys();
    // åŠé€‰ä¸­çš„部门节点
    let halfCheckedKeys = deptRef.value.getHalfCheckedKeys();
    checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys);
    return checkedKeys
const getDeptAllCheckedKeys = (): any => {
  // ç›®å‰è¢«é€‰ä¸­çš„部门节点
  let checkedKeys = deptRef.value?.getCheckedKeys();
  // åŠé€‰ä¸­çš„部门节点
  let halfCheckedKeys = deptRef.value?.getHalfCheckedKeys();
  if (halfCheckedKeys) {
    checkedKeys?.unshift.apply(checkedKeys, halfCheckedKeys);
  }
  return checkedKeys
}
/** é‡ç½®æ–°å¢žçš„表单以及其他数据  */
const reset = () => {
    menuRef.value.setCheckedKeys([]);
    menuExpand.value = false
    menuNodeAll.value = false
    deptExpand.value = true
    deptNodeAll.value = false
    form.value = { ...initForm };
    roleFormRef.value.resetFields();
  menuRef.value?.setCheckedKeys([]);
  menuExpand.value = false
  menuNodeAll.value = false
  deptExpand.value = true
  deptNodeAll.value = false
  form.value = { ...initForm };
  roleFormRef.value?.resetFields();
}
/** æ·»åŠ è§’è‰² */
const handleAdd = () => {
    dialog.visible = true;
    dialog.title = "添加角色";
    nextTick(() => {
        reset();
        getMenuTreeselect();
    })
  dialog.visible = true;
  dialog.title = "添加角色";
  nextTick(() => {
    reset();
    getMenuTreeselect();
  })
}
/** ä¿®æ”¹è§’色 */
const handleUpdate = async (row?: RoleVO) => {
    const roleId = row?.roleId || ids.value[0]
    const roleMenu = getRoleMenuTreeselect(roleId)
    const { data } = await getRole(roleId);
    dialog.visible = true;
    dialog.title = "修改角色";
    nextTick(() => {
        reset();
        Object.assign(form.value, data);
        form.value.roleSort = Number(form.value.roleSort);
        nextTick(async () => {
            const res = await roleMenu;
            let checkedKeys = res.checkedKeys;
            checkedKeys.forEach((v) => {
                nextTick(() => {
                    menuRef.value.setChecked(v, true, false);
                })
            })
  const roleId = row?.roleId || ids.value[0]
  const roleMenu = getRoleMenuTreeselect(roleId)
  const { data } = await getRole(roleId);
  dialog.visible = true;
  dialog.title = "修改角色";
  await nextTick(() => {
    reset();
    Object.assign(form.value, data);
    form.value.roleSort = Number(form.value.roleSort);
    nextTick(async () => {
      const res = await roleMenu;
      let checkedKeys = res.checkedKeys;
      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;
    })
  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 res = await deptTreeSelect(roleId);
  deptOptions.value = res.data.depts;
  return res.data;
}
/** æ ‘权限(展开/折叠)*/
const handleCheckedTreeExpand = (value: any, type: string) => {
    if (type == "menu") {
        let treeList = menuOptions.value;
        for (let i = 0; i < treeList.length; i++) {
            menuRef.value.store.nodesMap[treeList[i].id].expanded = value;
        }
    } else if (type == "dept") {
        let treeList = deptOptions.value;
        for (let i = 0; i < treeList.length; i++) {
            deptRef.value.store.nodesMap[treeList[i].id].expanded = value;
        }
const handleCheckedTreeExpand = (value: boolean, type: string) => {
  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") {
    let treeList = deptOptions.value;
    for (let i = 0; i < treeList.length; i++) {
      if (deptRef.value) {
        deptRef.value.store.nodesMap[treeList[i].id].expanded = value;
      }
    }
  }
}
/** æ ‘权限(全选/全不选) */
const handleCheckedTreeNodeAll = (value: any, type: string) => {
    if (type == "menu") {
        menuRef.value.setCheckedNodes(value ? menuOptions.value : []);
    } else if (type == "dept") {
        deptRef.value.setCheckedNodes(value ? deptOptions.value : []);
    }
  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") {
        form.value.menuCheckStrictly = value;
    } else if (type == "dept") {
        form.value.deptCheckStrictly = value;
    }
  if (type == "menu") {
    form.value.menuCheckStrictly = value;
  } else if (type == "dept") {
    form.value.deptCheckStrictly = value;
  }
}
/** æ‰€æœ‰èœå•节点数据 */
const getMenuAllCheckedKeys = () => {
    // ç›®å‰è¢«é€‰ä¸­çš„菜单节点
    let checkedKeys = menuRef.value.getCheckedKeys();
    // åŠé€‰ä¸­çš„菜单节点
    let halfCheckedKeys = menuRef.value.getHalfCheckedKeys();
    checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys);
    return checkedKeys;
const getMenuAllCheckedKeys = (): any => {
  // ç›®å‰è¢«é€‰ä¸­çš„菜单节点
  let checkedKeys = menuRef.value?.getCheckedKeys();
  // åŠé€‰ä¸­çš„菜单节点
  let halfCheckedKeys = menuRef.value?.getHalfCheckedKeys();
  if (halfCheckedKeys) {
    checkedKeys?.unshift.apply(checkedKeys, halfCheckedKeys);
  }
  return checkedKeys;
}
/** æäº¤æŒ‰é’® */
const submitForm = () => {
    roleFormRef.value.validate(async (valid: boolean) => {
        if (valid) {
            form.value.menuIds = getMenuAllCheckedKeys()
            form.value.roleId ? await updateRole(form.value) : await addRole(form.value);
            proxy?.$modal.msgSuccess("操作成功")
            dialog.visible = false
            getList()
        }
    })
  roleFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      form.value.menuIds = getMenuAllCheckedKeys()
      form.value.roleId ? await updateRole(form.value) : await addRole(form.value);
      proxy?.$modal.msgSuccess("操作成功")
      dialog.visible = false
      getList()
    }
  })
}
/** å–消按钮 */
const cancel = () => {
    reset()
    dialog.visible = false;
  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 roleDeptTreeselect = getRoleDeptTreeSelect(row.roleId);
    const response = await getRole(row.roleId);
    Object.assign(form.value, response.data);
    openDataScope.value = true;
    dialog.title = "分配数据权限";
    nextTick(async () => {
        const res = await roleDeptTreeselect;
        nextTick(() => {
            if (deptRef.value) {
                deptRef.value.setCheckedKeys(res.checkedKeys);
            }
        })
  const roleDeptTreeselect = getRoleDeptTreeSelect(row.roleId);
  const response = await getRole(row.roleId);
  Object.assign(form.value, response.data);
  openDataScope.value = true;
  dialog.title = "分配数据权限";
  await nextTick(async () => {
    const res = await roleDeptTreeselect;
    await nextTick(() => {
      if (deptRef.value) {
        deptRef.value.setCheckedKeys(res.checkedKeys);
      }
    })
  })
}
/** æäº¤æŒ‰é’®ï¼ˆæ•°æ®æƒé™ï¼‰ */
const submitDataScope = async () => {
    if (form.value.roleId) {
        form.value.deptIds = getDeptAllCheckedKeys();
        await dataScope(form.value);
        proxy?.$modal.msgSuccess("修改成功");
        openDataScope.value = false;
        getList();
    }
  if (form.value.roleId) {
    form.value.deptIds = getDeptAllCheckedKeys();
    await dataScope(form.value);
    proxy?.$modal.msgSuccess("修改成功");
    openDataScope.value = false;
    getList();
  }
}
/** å–消按钮(数据权限)*/
const cancelDataScope = () => {
    dataScopeRef.value.resetFields();
    form.value = {...initForm};
    openDataScope.value = false;
  dataScopeRef.value?.resetFields();
  form.value = { ...initForm };
  openDataScope.value = false;
}
onMounted(() => {
    getList();
  getList();
});
</script>
src/views/system/role/selectUser.vue
@@ -47,14 +47,12 @@
import { authUserSelectAll, unallocatedUserList } from "@/api/system/role";
import { UserVO } from '@/api/system/user/types';
import { UserQuery } from '@/api/system/user/types';
import { ComponentInternalInstance } from 'vue';
import { ElForm, ElTable } from 'element-plus';
const props = defineProps({
    roleId: {
        type: [Number, String]
    }
  roleId: {
    type: [Number, String]
  }
})
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -66,67 +64,68 @@
const userIds = ref<Array<string | number>>([]);
const queryParams = reactive<UserQuery>({
    pageNum: 1,
    pageSize: 10,
    roleId: undefined,
    userName: undefined,
    phonenumber: undefined
  pageNum: 1,
  pageSize: 10,
  roleId: undefined,
  userName: undefined,
  phonenumber: undefined
})
const tableRef = ref(ElTable);
const queryFormRef = ref(ElForm);
const tableRef = ref<ElTableInstance>();
const queryFormRef = ref<ElFormInstance>();
const show = () => {
    queryParams.roleId = props.roleId;
    getList();
    visible.value = true;
  queryParams.roleId = props.roleId;
  getList();
  visible.value = true;
}
/**
 * é€‰æ‹©è¡Œ
 */
const clickRow = (row: any) => {
    tableRef.value.toggleRowSelection(row);
  // ele的bug
  tableRef.value?.toggleRowSelection(row);
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: UserVO[]) => {
    userIds.value = selection.map((item: UserVO) => item.userId);
  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 res = await unallocatedUserList(queryParams);
  userList.value = res.rows;
  total.value = res.total;
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    queryParams.pageNum = 1;
    getList();
  queryParams.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
    queryFormRef.value.resetFields();
    getList();
  queryFormRef.value?.resetFields();
  getList();
}
const emit = defineEmits(["ok"]);
/**选择授权用户操作 */
const handleSelectUser = async () => {
    const roleId = queryParams.roleId;
    const ids = userIds.value.join(',');
    if (ids == "") {
        proxy?.$modal.msgError('请选择要分配的用户');
        return;
    }
    await authUserSelectAll({ roleId, userIds: ids });
    proxy?.$modal.msgSuccess('分配成功');
    emit('ok');
    visible.value = false;
  const roleId = queryParams.roleId;
  const ids = userIds.value.join(',');
  if (ids == "") {
    proxy?.$modal.msgError('请选择要分配的用户');
    return;
  }
  await authUserSelectAll({ roleId, userIds: ids });
  proxy?.$modal.msgSuccess('分配成功');
  emit('ok');
  visible.value = false;
}
// æš´éœ²
defineExpose({
    show,
  show,
});
</script>
src/views/system/tenant/index.vue
@@ -1,29 +1,31 @@
<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="68px">
          <el-form-item label="租户编号" prop="tenantId">
            <el-input v-model="queryParams.tenantId" placeholder="请输入租户编号" clearable @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="联系人" prop="contactUserName">
            <el-input v-model="queryParams.contactUserName" placeholder="请输入联系人" clearable @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="联系电话" prop="contactPhone">
            <el-input v-model="queryParams.contactPhone" placeholder="请输入联系电话" clearable @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="企业名称" prop="companyName">
            <el-input v-model="queryParams.companyName" placeholder="请输入企业名称" clearable @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      <div class="mb-[10px]" v-show="showSearch">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
            <el-form-item label="租户编号" prop="tenantId">
              <el-input v-model="queryParams.tenantId" placeholder="请输入租户编号" clearable @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="联系人" prop="contactUserName">
              <el-input v-model="queryParams.contactUserName" placeholder="请输入联系人" clearable @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="联系电话" prop="contactPhone">
              <el-input v-model="queryParams.contactPhone" placeholder="请输入联系电话" clearable @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="企业名称" prop="companyName">
              <el-input v-model="queryParams.companyName" placeholder="请输入企业名称" clearable @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item>
              <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>
      </div>
    </transition>
    <el-card shadow="never">
    <el-card shadow="hover">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
@@ -80,7 +82,7 @@
        </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" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹ç§Ÿæˆ·å¯¹è¯æ¡† -->
    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
@@ -143,8 +145,6 @@
import { selectTenantPackage } from '@/api/system/tenantPackage';
import { TenantForm, TenantQuery, TenantVO } from '@/api/system/tenant/types';
import { TenantPkgVO } from '@/api/system/tenantPackage/types';
import { ComponentInternalInstance } from 'vue';
import { ElForm } from 'element-plus';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -158,196 +158,196 @@
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref(ElForm);
const tenantFormRef = ref(ElForm);
const queryFormRef = ref<ElFormInstance>();
const tenantFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
    visible: false,
    title: ''
  visible: false,
  title: ''
});
const initFormData: TenantForm = {
    id: undefined,
    tenantId: undefined,
    contactUserName: '',
    contactPhone: '',
    username: '',
    password: '',
    companyName: '',
    licenseNumber: '',
    domain: '',
    address: '',
    intro: '',
    remark: '',
    packageId: '',
    expireTime: '',
    accountCount: 0,
    status: '0',
  id: undefined,
  tenantId: undefined,
  contactUserName: '',
  contactPhone: '',
  username: '',
  password: '',
  companyName: '',
  licenseNumber: '',
  domain: '',
  address: '',
  intro: '',
  remark: '',
  packageId: '',
  expireTime: '',
  accountCount: 0,
  status: '0',
}
const data = reactive<PageData<TenantForm, TenantQuery>>({
    form: {...initFormData},
    queryParams: {
        pageNum: 1,
        pageSize: 10,
        tenantId: '',
        contactUserName: '',
        contactPhone: '',
        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" }],
        username: [
            { required: true, message: "用户名不能为空", trigger: "blur" },
            { min: 2, max: 20, message: '用户名称长度必须介于 2 å’Œ 20 ä¹‹é—´', trigger: 'blur' }
        ],
        password: [
            { required: true, message: "密码不能为空", trigger: "blur" },
            { min: 5, max: 20, message: '用户密码长度必须介于 5 å’Œ 20 ä¹‹é—´', trigger: 'blur' }
        ]
    }
  form: { ...initFormData },
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    tenantId: '',
    contactUserName: '',
    contactPhone: '',
    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" }],
    username: [
      { required: true, message: "用户名不能为空", trigger: "blur" },
      { min: 2, max: 20, message: '用户名称长度必须介于 2 å’Œ 20 ä¹‹é—´', trigger: 'blur' }
    ],
    password: [
      { required: true, message: "密码不能为空", trigger: "blur" },
      { min: 5, max: 20, message: '用户密码长度必须介于 5 å’Œ 20 ä¹‹é—´', trigger: 'blur' }
    ]
  }
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢æ‰€æœ‰ç§Ÿæˆ·å¥—餐 */
const getTenantPackage = async () => {
    const res = await selectTenantPackage()
    packageList.value = res.data;
  const res = await selectTenantPackage()
  packageList.value = res.data;
}
/** æŸ¥è¯¢ç§Ÿæˆ·åˆ—表 */
const getList = async () => {
    loading.value = true;
    const res = await listTenant(queryParams.value);
    tenantList.value = res.rows;
    total.value = res.total;
    loading.value = false;
  loading.value = true;
  const res = await listTenant(queryParams.value);
  tenantList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
// ç§Ÿæˆ·å¥—餐状态修改
const handleStatusChange = async (row: TenantVO) => {
    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 + "成功");
    } catch {
        row.status = row.status === "0" ? "1" : "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 + "成功");
  } catch {
    row.status = row.status === "0" ? "1" : "0";
  }
}
// å–消按钮
const cancel = () => {
    reset();
    dialog.visible = false;
  reset();
  dialog.visible = false;
}
// è¡¨å•重置
const reset = () => {
    form.value = {...initFormData};
    tenantFormRef.value.resetFields();
  form.value = { ...initFormData };
  tenantFormRef.value?.resetFields();
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    queryParams.value.pageNum = 1;
    getList();
  queryParams.value.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
    queryFormRef.value.resetFields();
    handleQuery();
  queryFormRef.value?.resetFields();
  handleQuery();
}
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
const handleSelectionChange = (selection: TenantVO[]) => {
    ids.value = selection.map(item => item.id);
    single.value = selection.length != 1;
    multiple.value = !selection.length;
  ids.value = selection.map(item => item.id);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
    dialog.visible = true;
    dialog.title = "添加租户";
    nextTick(() => {
        reset();
        getTenantPackage();
    })
  dialog.visible = true;
  dialog.title = "添加租户";
  nextTick(() => {
    reset();
    getTenantPackage();
  })
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = (row?: TenantVO) => {
    loading.value = true;
    dialog.visible = true;
    dialog.title = "修改租户";
    nextTick(async () => {
        reset();
        getTenantPackage();
        const _id = row?.id || ids.value[0];
        const res = await getTenant(_id);
        loading.value = false;
        Object.assign(form.value, res.data)
    })
  loading.value = true;
  dialog.visible = true;
  dialog.title = "修改租户";
  nextTick(async () => {
    reset();
    await getTenantPackage();
    const _id = row?.id || ids.value[0];
    const res = await getTenant(_id);
    loading.value = false;
    Object.assign(form.value, res.data)
  })
}
/** æäº¤æŒ‰é’® */
const submitForm = () => {
    tenantFormRef.value.validate(async (valid: boolean) => {
        if (valid) {
            buttonLoading.value = true;
            if (form.value.id) {
                await updateTenant(form.value).finally(() => buttonLoading.value = false);
            } else {
                await addTenant(form.value).finally(() => buttonLoading.value = false);
            }
            proxy?.$modal.msgSuccess("操作成功");
            dialog.visible = false;
            getList();
        }
    });
  tenantFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      buttonLoading.value = true;
      if (form.value.id) {
        await updateTenant(form.value).finally(() => buttonLoading.value = false);
      } else {
        await addTenant(form.value).finally(() => buttonLoading.value = false);
      }
      proxy?.$modal.msgSuccess("操作成功");
      dialog.visible = false;
      getList();
    }
  });
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: TenantVO) => {
    const _ids = row?.id || ids.value;
    await proxy?.$modal.confirm('是否确认删除租户编号为"' + _ids + '"的数据项?')
    loading.value = true;
    await delTenant(_ids).finally(() => loading.value = false);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
  const _ids = row?.id || ids.value;
  await proxy?.$modal.confirm('是否确认删除租户编号为"' + _ids + '"的数据项?')
  loading.value = true;
  await delTenant(_ids).finally(() => loading.value = false);
  await getList();
  proxy?.$modal.msgSuccess("删除成功");
}
/** åŒæ­¥ç§Ÿæˆ·å¥—餐按钮操作 */
const handleSyncTenantPackage = async (row: TenantVO) => {
    try {
        await proxy?.$modal.confirm('是否确认同步租户套餐租户编号为"' + row.tenantId + '"的数据项?');
        loading.value = true;
        await syncTenantPackage(row.tenantId, row.packageId);
        getList();
        proxy?.$modal.msgSuccess("同步成功");
    } catch {return} finally {
        loading.value = false;
    }
  try {
    await proxy?.$modal.confirm('是否确认同步租户套餐租户编号为"' + row.tenantId + '"的数据项?');
    loading.value = true;
    await syncTenantPackage(row.tenantId, row.packageId);
    await getList();
    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();
  getList();
})
</script>
src/views/system/tenantPackage/index.vue
@@ -1,37 +1,39 @@
<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="68px">
          <el-form-item label="套餐名称" prop="packageName">
            <el-input v-model="queryParams.packageName" placeholder="请输入套餐名称" clearable @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      <div class="mb-[10px]" v-show="showSearch">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
            <el-form-item label="套餐名称" prop="packageName">
              <el-input v-model="queryParams.packageName" placeholder="请输入套餐名称" clearable @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item>
              <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>
      </div>
    </transition>
    <el-card shadow="never">
    <el-card shadow="hover">
      <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 type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:tenantPackage:add']"> æ–°å¢ž </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
            >
            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:tenantPackage:edit']">
              ä¿®æ”¹
            </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
            >
            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:tenantPackage:remove']">
              åˆ é™¤
            </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 type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:tenantPackage:export']">导出 </el-button>
          </el-col>
          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
        </el-row>
@@ -53,13 +55,13 @@
              <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:tenantPackage:edit']"></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 link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:tenantPackage:remove']"></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" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-card>
    <!-- æ·»åŠ æˆ–ä¿®æ”¹ç§Ÿæˆ·å¥—é¤å¯¹è¯æ¡† -->
@@ -70,8 +72,8 @@
        </el-form-item>
        <el-form-item label="关联菜单">
          <el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">展开/折叠</el-checkbox>
          <el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选</el-checkbox>
          <el-checkbox v-model="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')">父子联动</el-checkbox>
          <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
            class="tree-border"
            :data="menuOptions"
@@ -98,12 +100,17 @@
</template>
<script setup name="TenantPackage" lang="ts">
import { listTenantPackage, getTenantPackage, delTenantPackage, addTenantPackage, updateTenantPackage, changePackageStatus } from "@/api/system/tenantPackage";
import {
  listTenantPackage,
  getTenantPackage,
  delTenantPackage,
  addTenantPackage,
  updateTenantPackage,
  changePackageStatus
} from "@/api/system/tenantPackage";
import { treeselect as menuTreeselect, tenantPackageMenuTreeselect } from "@/api/system/menu";
import { ComponentInternalInstance } from "vue";
import { TenantPkgForm, TenantPkgQuery, TenantPkgVO } from "@/api/system/tenantPackage/types";
import { MenuTreeOption } from "@/api/system/menu/types";
import { CheckboxValueType, ElTree, ElForm } from 'element-plus';
import to from "await-to-js";
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -120,212 +127,216 @@
const menuNodeAll = ref(false);
const menuOptions = ref<MenuTreeOption[]>([]);
const menuTreeRef = ref(ElTree);
const queryFormRef = ref(ElForm);
const tenantPackageFormRef = ref(ElForm);
const menuTreeRef = ref<ElTreeInstance>();
const queryFormRef = ref<ElFormInstance>();
const tenantPackageFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
    visible: false,
    title: ''
  visible: false,
  title: ""
});
const initFormData: TenantPkgForm = {
    packageId: undefined,
    packageName: '',
    menuIds: '',
    remark: '',
    menuCheckStrictly: true
  packageId: undefined,
  packageName: "",
  menuIds: "",
  remark: "",
  menuCheckStrictly: true
};
const data = reactive<PageData<TenantPkgForm, TenantPkgQuery>>({
    form: {...initFormData},
    queryParams: {
        pageNum: 1,
        pageSize: 10,
        packageName: ''
    },
    rules: {
        packageId: [{ required: true, message: "租户套餐id不能为空", trigger: "blur" }],
        packageName: [{ required: true, message: "套餐名称不能为空", trigger: "blur" }]
    }
  form: { ...initFormData },
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    packageName: ""
  },
  rules: {
    packageId: [{ required: true, message: "租户套餐id不能为空", trigger: "blur" }],
    packageName: [{ required: true, message: "套餐名称不能为空", trigger: "blur" }]
  }
});
const { queryParams, form, rules } = toRefs(data);
/** æŸ¥è¯¢èœå•树结构 */
const getMenuTreeselect = async() => {
    const { data } = await menuTreeselect();
    menuOptions.value = data;
}
const getMenuTreeselect = async () => {
  const { data } = await menuTreeselect();
  menuOptions.value = data;
};
// æ‰€æœ‰èœå•节点数据
const getMenuAllCheckedKeys = () => {
    // ç›®å‰è¢«é€‰ä¸­çš„菜单节点
    let checkedKeys = menuTreeRef.value.getCheckedKeys();
    // åŠé€‰ä¸­çš„菜单节点
    let halfCheckedKeys = menuTreeRef.value.getHalfCheckedKeys();
    checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys);
    return checkedKeys;
}
const getMenuAllCheckedKeys = (): any => {
  // ç›®å‰è¢«é€‰ä¸­çš„菜单节点
  let checkedKeys = menuTreeRef.value?.getCheckedKeys();
  // åŠé€‰ä¸­çš„菜单节点
  let halfCheckedKeys = menuTreeRef.value?.getHalfCheckedKeys();
  if (halfCheckedKeys) {
    checkedKeys?.unshift.apply(checkedKeys, halfCheckedKeys);
  }
  return checkedKeys;
};
/** æ ¹æ®ç§Ÿæˆ·å¥—餐ID查询菜单树结构 */
const getPackageMenuTreeselect = async(packageId: string | number) => {
    const res = await tenantPackageMenuTreeselect(packageId);
    menuOptions.value = res.data.menus;
    return Promise.resolve(res);
}
const getPackageMenuTreeselect = async (packageId: string | number) => {
  const res = await tenantPackageMenuTreeselect(packageId);
  menuOptions.value = res.data.menus;
  return Promise.resolve(res);
};
/** æŸ¥è¯¢ç§Ÿæˆ·å¥—餐列表 */
const getList = async () => {
    loading.value = true;
    const res = await listTenantPackage(queryParams.value);
    tenantPackageList.value = res.rows;
    total.value = res.total;
    loading.value = false;
}
  loading.value = true;
  const res = await listTenantPackage(queryParams.value);
  tenantPackageList.value = res.rows;
  total.value = res.total;
  loading.value = false;
};
// ç§Ÿæˆ·å¥—餐状态修改
const handleStatusChange = async (row: TenantPkgVO) => {
    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";
    } else {
        await changePackageStatus(row.packageId, row.status);
        proxy?.$modal.msgSuccess(text + "成功");
    }
}
  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";
  } else {
    await changePackageStatus(row.packageId, row.status);
    proxy?.$modal.msgSuccess(text + "成功");
  }
};
// å–消按钮
const cancel = () => {
    reset();
    dialog.visible = false;
}
  reset();
  dialog.visible = false;
};
// è¡¨å•重置
const reset = () => {
    menuTreeRef.value.setCheckedKeys([]);
    menuExpand.value = false;
    menuNodeAll.value = false;
    form.value = {...initFormData};
    tenantPackageFormRef.value.resetFields();
}
  menuTreeRef.value?.setCheckedKeys([]);
  menuExpand.value = false;
  menuNodeAll.value = false;
  form.value = { ...initFormData };
  tenantPackageFormRef.value?.resetFields();
};
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    queryParams.value.pageNum = 1;
    getList();
}
  queryParams.value.pageNum = 1;
  getList();
};
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
    queryFormRef.value.resetFields();
    handleQuery();
}
  queryFormRef.value?.resetFields();
  handleQuery();
};
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
const handleSelectionChange = (selection: TenantPkgVO[]) => {
    ids.value = selection.map(item => item.packageId);
    single.value = selection.length != 1;
    multiple.value = !selection.length;
}
  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') {
        let treeList = menuOptions.value;
        for (let i = 0; i < treeList.length; i++) {
            menuTreeRef.value.store.nodesMap[treeList[i].id].expanded = value;
        }
  if (type == "menu") {
    let treeList = menuOptions.value;
    for (let i = 0; i < treeList.length; i++) {
      if (menuTreeRef.value) {
        menuTreeRef.value.store.nodesMap[treeList[i].id].expanded = value as boolean;
      }
    }
}
  }
};
// æ ‘权限(全选/全不选)
const handleCheckedTreeNodeAll = (value: CheckboxValueType, type: string) => {
    if (type == 'menu') {
        menuTreeRef.value.setCheckedNodes(value ? menuOptions.value: []);
    }
}
  if (type == "menu") {
    menuTreeRef.value?.setCheckedNodes(value ? menuOptions.value as any : []);
  }
};
// æ ‘权限(父子联动)
const handleCheckedTreeConnect = (value: CheckboxValueType, type: string) => {
    if (type == 'menu') {
        form.value.menuCheckStrictly = value as boolean;
    }
}
  if (type == "menu") {
    form.value.menuCheckStrictly = value as boolean;
  }
};
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
    dialog.visible = true;
    dialog.title = "添加租户套餐";
    nextTick(() => {
        reset();
        getMenuTreeselect();
    })
}
  dialog.visible = true;
  dialog.title = "添加租户套餐";
  nextTick(() => {
    reset();
    getMenuTreeselect();
  });
};
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = (row?: TenantPkgVO) => {
    loading.value = true
    dialog.visible = true;
    dialog.title = "修改租户套餐";
    nextTick(async () => {
        reset();
        const _packageId = row?.packageId || ids.value[0];
        const packageMenu = getPackageMenuTreeselect(_packageId);
        const response = await getTenantPackage(_packageId);
        loading.value = false;
        form.value = response.data;
        nextTick(async () => {
            const res = await packageMenu;
            let checkedKeys = res.data.checkedKeys
            checkedKeys.forEach((v) => {
                nextTick(() => {
                    menuTreeRef.value.setChecked(v, true ,false);
                })
            })
  loading.value = true;
  dialog.visible = true;
  dialog.title = "修改租户套餐";
  nextTick(async () => {
    reset();
    const _packageId = row?.packageId || ids.value[0];
    const packageMenu = getPackageMenuTreeselect(_packageId);
    const response = await getTenantPackage(_packageId);
    loading.value = false;
    form.value = response.data;
    await nextTick(async () => {
      const res = await packageMenu;
      let checkedKeys = res.data.checkedKeys;
      checkedKeys.forEach((v) => {
        nextTick(() => {
          menuTreeRef.value?.setChecked(v, true, false);
        });
    })
}
      });
    });
  });
};
/** æäº¤æŒ‰é’® */
const submitForm = () => {
    tenantPackageFormRef.value.validate(async (valid: boolean) => {
        if (valid) {
            buttonLoading.value = true;
            form.value.menuIds = getMenuAllCheckedKeys();
            if (form.value.packageId != null) {
                await updateTenantPackage(form.value).finally(() => buttonLoading.value = false);
            } else {
                await addTenantPackage(form.value).finally(() => buttonLoading.value = false);
            }
            proxy?.$modal.msgSuccess("操作成功");
            dialog.visible = false;
            getList();
        }
    });
}
  tenantPackageFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      buttonLoading.value = true;
      form.value.menuIds = getMenuAllCheckedKeys();
      if (form.value.packageId != null) {
        await updateTenantPackage(form.value).finally(() => buttonLoading.value = false);
      } else {
        await addTenantPackage(form.value).finally(() => buttonLoading.value = false);
      }
      proxy?.$modal.msgSuccess("操作成功");
      dialog.visible = false;
      await getList();
    }
  });
};
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: TenantPkgVO) => {
    const _packageIds = row?.packageId || ids.value;
    await proxy?.$modal.confirm('是否确认删除租户套餐编号为"' + _packageIds + '"的数据项?').finally(() => {
        loading.value = false;
    });
    await delTenantPackage(_packageIds);
    loading.value = true;
    getList();
    proxy?.$modal.msgSuccess("删除成功");
}
  const _packageIds = row?.packageId || ids.value;
  await proxy?.$modal.confirm("是否确认删除租户套餐编号为\"" + _packageIds + "\"的数据项?").finally(() => {
    loading.value = false;
  });
  await delTenantPackage(_packageIds);
  loading.value = true;
  await getList();
  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(() => {
    getList();
})
  getList();
});
</script>
src/views/system/user/authRole.vue
@@ -55,11 +55,10 @@
</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 { ElTable } from "element-plus";
import { ComponentInternalInstance } from 'vue';
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,58 +69,59 @@
const roleIds = ref<Array<string | number>>([]);
const roles = ref<RoleVO[]>([]);
const form = ref<Partial<UserForm>>({
    nickName: undefined,
    userName: '',
    userId: undefined
  nickName: undefined,
  userName: "",
  userId: undefined
});
const tableRef = ref(ElTable)
const tableRef = ref<ElTableInstance>();
/** å•击选中行数据 */
const clickRow = (row: RoleVO) => {
    tableRef.value.toggleRowSelection(row);
  // ele的方法有问题,selected应该为可选参数
  tableRef.value?.toggleRowSelection(row);
};
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
const handleSelectionChange = (selection: RoleVO[]) => {
    roleIds.value = selection.map(item => item.roleId);
  roleIds.value = selection.map(item => item.roleId);
};
/** ä¿å­˜é€‰ä¸­çš„æ•°æ®ç¼–号 */
const getRowKey = (row: RoleVO): string => {
    return String(row.roleId);
  return String(row.roleId);
};
/** å…³é—­æŒ‰é’® */
const close = () => {
    const obj = { path: "/system/user" };
    proxy?.$tab.closeOpenPage(obj);
  const obj = { path: "/system/user" };
  proxy?.$tab.closeOpenPage(obj);
};
/** æäº¤æŒ‰é’® */
const submitForm = async () => {
    const userId = form.value.userId;
    const rIds = roleIds.value.join(",");
    await updateAuthRole({ userId: userId as string, roleIds: rIds })
    proxy?.$modal.msgSuccess("授权成功");
    close();
  const userId = form.value.userId;
  const rIds = roleIds.value.join(",");
  await updateAuthRole({ userId: userId as string, roleIds: rIds });
  proxy?.$modal.msgSuccess("授权成功");
  close();
};
const getList = async() => {
    const userId = route.params && route.params.userId;
    if (userId) {
        loading.value = true;
        const res = await getAuthRole(userId as string);
        Object.assign(form.value, res.data.user)
        Object.assign(roles.value, res.data.roles)
        total.value = roles.value.length;
        await nextTick(() => {
            roles.value.forEach(row => {
                if (row?.flag) {
                    tableRef.value.toggleRowSelection(row);
                }
            });
        });
        loading.value = false;
    }
}
const getList = async () => {
  const userId = route.params && route.params.userId;
  if (userId) {
    loading.value = true;
    const res = await getAuthRole(userId as string);
    Object.assign(form.value, res.data.user);
    Object.assign(roles.value, res.data.roles);
    total.value = roles.value.length;
    await nextTick(() => {
      roles.value.forEach(row => {
        if (row?.flag) {
          tableRef.value?.toggleRowSelection(row, true);
        }
      });
    });
    loading.value = false;
  }
};
onMounted(() => {
    getList();
})
  getList();
});
</script>
src/views/system/user/index.vue
@@ -16,40 +16,48 @@
            highlight-current
            default-expand-all
            @node-click="handleNodeClick"
          ></el-tree>
          />
        </el-card>
      </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="search" v-show="showSearch">
            <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
              <el-form-item label="用户名称" prop="userName">
                <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
              </el-form-item>
              <el-form-item label="手机号码" prop="phonenumber">
                <el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable style="width: 240px" @keyup.enter="handleQuery" />
              </el-form-item>
          <div class="mb-[10px]" v-show="showSearch">
            <el-card shadow="hover">
              <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="68px">
                <el-form-item label="用户名称" prop="userName">
                  <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable style="width: 240px" @keyup.enter="handleQuery" />
                </el-form-item>
                <el-form-item label="手机号码" prop="phonenumber">
                  <el-input
                    v-model="queryParams.phonenumber"
                    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">
                  <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-date-picker
                  v-model="dateRange"
                  value-format="YYYY-MM-DD"
                  type="daterange"
                  range-separator="-"
                  start-placeholder="开始日期"
                  end-placeholder="结束日期"
                ></el-date-picker>
              </el-form-item>
              <el-form-item>
                <el-button type="primary" @click="handleQuery" icon="Search">搜索</el-button>
                <el-button @click="resetQuery" icon="Refresh">重置</el-button>
              </el-form-item>
            </el-form>
                <el-form-item label="状态" prop="status">
                  <el-select v-model="queryParams.status" placeholder="用户状态" clearable style="width: 240px">
                    <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-date-picker
                    v-model="dateRange"
                    value-format="YYYY-MM-DD"
                    type="daterange"
                    range-separator="-"
                    start-placeholder="开始日期"
                    end-placeholder="结束日期"
                  ></el-date-picker>
                </el-form-item>
                <el-form-item>
                  <el-button type="primary" @click="handleQuery" icon="Search">搜索</el-button>
                  <el-button @click="resetQuery" icon="Refresh">重置</el-button>
                </el-form-item>
              </el-form>
            </el-card>
          </div>
        </transition>
@@ -203,7 +211,7 @@
            <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>
                  dict.label }}</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
@@ -289,29 +297,18 @@
</template>
<script setup name="User" lang="ts">
import {
    changeUserStatus,
    listUser,
    resetUserPwd,
    delUser,
    getUser,
    updateUser,
    addUser,
    deptTreeSelect
} from "@/api/system/user"
import api from "@/api/system/user"
import { UserForm, UserQuery, UserVO } from '@/api/system/user/types';
import { ComponentInternalInstance } from "vue";
import { getToken } from "@/utils/auth";
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 { DateModelType, ElTree, ElUpload, UploadFile, ElForm } from 'element-plus';
import { to } from "await-to-js";
const router = useRouter();
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);
@@ -320,7 +317,7 @@
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['','']);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const deptName = ref('');
const deptOptions = ref<DeptVO[]>([]);
const initPassword = ref('123456');
@@ -328,274 +325,274 @@
const roleOptions = ref<RoleVO[]>([]);
/*** ç”¨æˆ·å¯¼å…¥å‚æ•° */
const upload = reactive<ImportOption>({
    // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚(用户导入)
    open: false,
    // å¼¹å‡ºå±‚标题(用户导入)
    title: "",
    // æ˜¯å¦ç¦ç”¨ä¸Šä¼ 
    isUploading: false,
    // æ˜¯å¦æ›´æ–°å·²ç»å­˜åœ¨çš„用户数据
    updateSupport: 0,
    // è®¾ç½®ä¸Šä¼ çš„请求头部
    headers: { Authorization: "Bearer " + getToken() },
    // ä¸Šä¼ çš„地址
    url: import.meta.env.VITE_APP_BASE_API + "/system/user/importData"
  // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚(用户导入)
  open: false,
  // å¼¹å‡ºå±‚标题(用户导入)
  title: "",
  // æ˜¯å¦ç¦ç”¨ä¸Šä¼ 
  isUploading: false,
  // æ˜¯å¦æ›´æ–°å·²ç»å­˜åœ¨çš„用户数据
  updateSupport: 0,
  // è®¾ç½®ä¸Šä¼ çš„请求头部
  headers: { Authorization: "Bearer " + getToken() },
  // ä¸Šä¼ çš„地址
  url: import.meta.env.VITE_APP_BASE_API + "/system/user/importData"
})
// åˆ—显隐信息
const columns = ref<FieldOption[]>([
    { key: 0, label: `用户编号`, visible: false },
    { key: 1, label: `用户名称`, visible: true },
    { key: 2, label: `用户昵称`, visible: true },
    { key: 3, label: `部门`, visible: true },
    { key: 4, label: `手机号码`, visible: true },
    { key: 5, label: `状态`, visible: true },
    { key: 6, label: `创建时间`, visible: true }
  { key: 0, label: `用户编号`, visible: false },
  { key: 1, label: `用户名称`, visible: true },
  { key: 2, label: `用户昵称`, visible: true },
  { key: 3, label: `部门`, visible: true },
  { key: 4, label: `手机号码`, visible: true },
  { key: 5, label: `状态`, visible: true },
  { key: 6, label: `创建时间`, visible: true }
])
const deptTreeRef = ref(ElTree);
const queryFormRef = ref(ElForm);
const userFormRef = ref(ElForm);
const uploadRef = ref(ElUpload);
const deptTreeRef = ref<ElTreeInstance>();
const queryFormRef = ref<ElFormInstance>();
const userFormRef = ref<ElFormInstance>();
const uploadRef = ref<ElUploadInstance>();
const dialog = reactive<DialogOption>({
    visible: false,
    title: ''
  visible: false,
  title: ''
});
const initFormData: UserForm = {
    userId: undefined,
    deptId: undefined,
    userName: '',
    nickName: undefined,
    password: '',
    phonenumber: undefined,
    email: undefined,
    sex: undefined,
    status: "0",
    remark: '',
    postIds: [],
    roleIds: []
  userId: undefined,
  deptId: undefined,
  userName: '',
  nickName: undefined,
  password: '',
  phonenumber: undefined,
  email: undefined,
  sex: undefined,
  status: "0",
  remark: '',
  postIds: [],
  roleIds: []
}
const data = reactive<PageData<UserForm, UserQuery>>({
    form: { ...initFormData },
    queryParams: {
        pageNum: 1,
        pageSize: 10,
        userName: '',
        phonenumber: '',
        status: '',
        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" }]
    }
  form: { ...initFormData },
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    userName: '',
    phonenumber: '',
    status: '',
    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" }]
  }
})
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);},
    {
        flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发,此属性控制在DOM元素更新后运行
    }
  () => { deptTreeRef.value?.filter(deptName.value); },
  {
    flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发,此属性控制在DOM元素更新后运行
  }
);
/** æŸ¥è¯¢éƒ¨é—¨ä¸‹æ‹‰æ ‘结构 */
const getTreeSelect = async () => {
    const res = await deptTreeSelect();
    deptOptions.value = res.data;
  const res = await api.deptTreeSelect();
  deptOptions.value = res.data;
};
/** æŸ¥è¯¢ç”¨æˆ·åˆ—表 */
const getList = async () => {
    loading.value = true;
    const res = await listUser(proxy?.addDateRange(queryParams.value, dateRange.value));
    loading.value = false;
    userList.value = res.rows;
    total.value = res.total;
  loading.value = true;
  const res = await api.listUser(proxy?.addDateRange(queryParams.value, dateRange.value));
  loading.value = false;
  userList.value = res.rows;
  total.value = res.total;
}
/** èŠ‚ç‚¹å•å‡»äº‹ä»¶ */
const handleNodeClick = (data: DeptVO) => {
    queryParams.value.deptId = data.id;
    handleQuery()
  queryParams.value.deptId = data.id;
  handleQuery()
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    queryParams.value.pageNum = 1
    getList()
  queryParams.value.pageNum = 1
  getList()
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
    dateRange.value = ['','']
    queryFormRef.value.resetFields();
    queryParams.value.pageNum = 1;
    queryParams.value.deptId = undefined;
    deptTreeRef.value.setCurrentKey(null);
    handleQuery();
  dateRange.value = ['', '']
  queryFormRef.value?.resetFields();
  queryParams.value.pageNum = 1;
  queryParams.value.deptId = undefined;
  deptTreeRef.value?.setCurrentKey(undefined);
  handleQuery();
}
/** åˆ é™¤æŒ‰é’®æ“ä½œ */
const handleDelete = async (row?: UserVO) => {
    const userIds = row?.userId || ids.value;
    const [err] = await to(proxy?.$modal.confirm('是否确认删除用户编号为"' + userIds + '"的数据项?') as any);
    if (!err) {
        await delUser(userIds);
        await getList();
        proxy?.$modal.msgSuccess("删除成功");
    }
  const userIds = row?.userId || ids.value;
  const [err] = await to(proxy?.$modal.confirm('是否确认删除用户编号为"' + userIds + '"的数据项?') as any);
  if (!err) {
    await api.delUser(userIds);
    await getList();
    proxy?.$modal.msgSuccess("删除成功");
  }
}
/** ç”¨æˆ·çŠ¶æ€ä¿®æ”¹  */
const handleStatusChange = async (row: UserVO) => {
    let text = row.status === "0" ? "启用" : "停用"
    try {
        await proxy?.$modal.confirm('确认要"' + text + '""' + row.userName + '"用户吗?');
        await changeUserStatus(row.userId, row.status);
        proxy?.$modal.msgSuccess(text + "成功");
    } catch (err) {
        row.status = row.status === "0" ? "1" : "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 + "成功");
  } catch (err) {
    row.status = row.status === "0" ? "1" : "0";
  }
}
/** è·³è½¬è§’色分配 */
const handleAuthRole = (row: UserVO) => {
    const userId = row.userId;
    router.push("/system/user-auth/role/" + userId);
  const userId = row.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 ä¹‹é—´",
    }))
    if (!err) {
        await resetUserPwd(row.userId, res.value);
        proxy?.$modal.msgSuccess("修改成功,新密码是:" + res.value);
    }
  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);
  }
}
/** é€‰æ‹©æ¡æ•°  */
const handleSelectionChange = (selection: UserVO[]) => {
    ids.value = selection.map((item) => item.userId);
    single.value = selection.length != 1;
    multiple.value = !selection.length;
  ids.value = selection.map((item) => item.userId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** å¯¼å…¥æŒ‰é’®æ“ä½œ */
const handleImport = () => {
    upload.title = "用户导入";
    upload.open = true;
  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;
  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 });
    getList();
  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 });
  getList();
}
/** æäº¤ä¸Šä¼ æ–‡ä»¶ */
function submitFileForm() {
    uploadRef.value.submit();
  uploadRef.value?.submit();
}
/** åˆå§‹åŒ–部门数据 */
const initTreeData = async () => {
    // åˆ¤æ–­éƒ¨é—¨çš„æ•°æ®æ˜¯å¦å­˜åœ¨ï¼Œå­˜åœ¨ä¸èŽ·å–ï¼Œä¸å­˜åœ¨åˆ™èŽ·å–
    if (deptOptions.value === undefined) {
        const { data } = await treeselect();
        deptOptions.value = data;
    }
  // åˆ¤æ–­éƒ¨é—¨çš„æ•°æ®æ˜¯å¦å­˜åœ¨ï¼Œå­˜åœ¨ä¸èŽ·å–ï¼Œä¸å­˜åœ¨åˆ™èŽ·å–
  if (deptOptions.value === undefined) {
    const { data } = await treeselect();
    deptOptions.value = data;
  }
}
/** é‡ç½®æ“ä½œè¡¨å• */
const reset = () => {
    form.value = { ...initFormData };
    userFormRef.value.resetFields();
  form.value = { ...initFormData };
  userFormRef.value?.resetFields();
}
/** å–消按钮 */
const cancel = () => {
    reset();
    dialog.visible = false;
  reset();
  dialog.visible = false;
}
/** æ–°å¢žæŒ‰é’®æ“ä½œ */
const handleAdd = () => {
    dialog.visible = true;
    dialog.title = "新增用户";
    nextTick(async () => {
        reset();
        await initTreeData();
        const { data } = await getUser();
        postOptions.value = data.posts;
        roleOptions.value = data.roles;
        form.value.password = initPassword.value;
    })
  dialog.visible = true;
  dialog.title = "新增用户";
  nextTick(async () => {
    reset();
    await initTreeData();
    const { data } = await api.getUser();
    postOptions.value = data.posts;
    roleOptions.value = data.roles;
    form.value.password = initPassword.value;
  })
}
/** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
const handleUpdate = (row?: UserForm) => {
    dialog.visible = true;
    dialog.title = "修改用户";
    nextTick(async () => {
        reset();
        await initTreeData();
        const userId = row?.userId || ids.value[0]
        const { data } = await getUser(userId)
        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 = "";
    })
  dialog.visible = true;
  dialog.title = "修改用户";
  nextTick(async () => {
    reset();
    await initTreeData();
    const userId = row?.userId || ids.value[0]
    const { data } = await api.getUser(userId)
    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 = "";
  })
}
/** æäº¤æŒ‰é’® */
const submitForm = () => {
    userFormRef.value.validate(async (valid: boolean) => {
        if (valid) {
            form.value.userId ? await updateUser(form.value) : await addUser(form.value);
            proxy?.$modal.msgSuccess("操作成功");
            dialog.visible = false;
            await getList();
        }
    })
  userFormRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      form.value.userId ? await api.updateUser(form.value) : await api.addUser(form.value);
      proxy?.$modal.msgSuccess("操作成功");
      dialog.visible = false;
      await getList();
    }
  })
}
@@ -603,23 +600,23 @@
 * å…³é—­ç”¨æˆ·å¼¹çª—
 */
const closeDialog = () => {
    dialog.visible = false;
    resetForm();
  dialog.visible = false;
  resetForm();
}
/**
 * é‡ç½®è¡¨å•
 */
const resetForm = () => {
    userFormRef.value.resetFields();
    userFormRef.value.clearValidate();
  userFormRef.value?.resetFields();
  userFormRef.value?.clearValidate();
    form.value.id = undefined;
    form.value.status = '1';
  form.value.id = undefined;
  form.value.status = '1';
}
onMounted(() => {
    getTreeSelect() // åˆå§‹åŒ–部门数据
    getList() // åˆå§‹åŒ–列表数据
  getTreeSelect() // åˆå§‹åŒ–部门数据
  getList() // åˆå§‹åŒ–列表数据
});
</script>
src/views/system/user/profile/index.vue
@@ -74,11 +74,11 @@
import { getUserProfile } from "@/api/system/user";
const activeTab = ref("userinfo");
const state = ref<{ user: any; roleGroup: string;  postGroup: string; auths:any[]}>({
const state = ref<Record<string, any>>({
    user: {},
    roleGroup: '',
    postGroup: '',
    auths: [],
    auths: []
});
const userForm = ref({});
src/views/system/user/profile/resetPwd.vue
@@ -17,46 +17,50 @@
</template>
<script setup lang="ts">
import { updateUserPwd } from '@/api/system/user';
import { ComponentInternalInstance } from 'vue';
import { ResetPwdForm } from '@/api/system/user/types'
import { ElForm } from 'element-plus';
import { updateUserPwd } from "@/api/system/user";
import type { ResetPwdForm } from "@/api/system/user/types";
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const pwdRef = ref(ElForm);
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("两次输入的密码不一致"));
    } else {
        callback();
    }
  if (user.value.newPassword !== value) {
    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"
  }]
});
/** æäº¤æŒ‰é’® */
const submit = () => {
    pwdRef.value.validate(async (valid: boolean) => {
        if (valid) {
            await updateUserPwd(user.value.oldPassword, user.value.newPassword)
            proxy?.$modal.msgSuccess("修改成功");
        }
    });
  pwdRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      await updateUserPwd(user.value.oldPassword, user.value.newPassword);
      proxy?.$modal.msgSuccess("修改成功");
    }
  });
};
/** å…³é—­æŒ‰é’® */
const close = () => {
    proxy?.$tab.closePage();
  proxy?.$tab.closePage();
};
</script>
src/views/system/user/profile/userAvatar.vue
@@ -29,7 +29,9 @@
          <el-upload action="#" :http-request="requestUpload" :show-file-list="false" :before-upload="beforeUpload">
            <el-button>
              é€‰æ‹©
              <el-icon class="el-icon--right"><Upload /></el-icon>
              <el-icon class="el-icon--right">
                <Upload />
              </el-icon>
            </el-button>
          </el-upload>
        </el-col>
@@ -58,18 +60,17 @@
import { VueCropper } from "vue-cropper";
import { uploadAvatar } from "@/api/system/user";
import useUserStore from "@/store/modules/user";
import { ComponentInternalInstance } from "vue";
interface Options {
    img: string | ArrayBuffer | null // è£å‰ªå›¾ç‰‡çš„地址
    autoCrop: boolean // æ˜¯å¦é»˜è®¤ç”Ÿæˆæˆªå›¾æ¡†
    autoCropWidth: number // é»˜è®¤ç”Ÿæˆæˆªå›¾æ¡†å®½åº¦
    autoCropHeight: number // é»˜è®¤ç”Ÿæˆæˆªå›¾æ¡†é«˜åº¦
    fixedBox: boolean // å›ºå®šæˆªå›¾æ¡†å¤§å° ä¸å…è®¸æ”¹å˜
    fileName: string
    previews: any // é¢„览数据
    outputType: string
    visible: boolean
  img: string | ArrayBuffer | null; // è£å‰ªå›¾ç‰‡çš„地址
  autoCrop: boolean; // æ˜¯å¦é»˜è®¤ç”Ÿæˆæˆªå›¾æ¡†
  autoCropWidth: number; // é»˜è®¤ç”Ÿæˆæˆªå›¾æ¡†å®½åº¦
  autoCropHeight: number; // é»˜è®¤ç”Ÿæˆæˆªå›¾æ¡†é«˜åº¦
  fixedBox: boolean; // å›ºå®šæˆªå›¾æ¡†å¤§å° ä¸å…è®¸æ”¹å˜
  fileName: string;
  previews: any; // é¢„览数据
  outputType: string;
  visible: boolean;
}
@@ -83,75 +84,76 @@
const cropper = ref<any>({});
//图片裁剪数据
const options = reactive<Options>({
    img: userStore.avatar,
    autoCrop: true,
    autoCropWidth: 200,
    autoCropHeight: 200,
    fixedBox: true,
    outputType: "png",
    fileName: '',
    previews: {},
    visible: false
  img: userStore.avatar,
  autoCrop: true,
  autoCropWidth: 200,
  autoCropHeight: 200,
  fixedBox: true,
  outputType: "png",
  fileName: "",
  previews: {},
  visible: false
});
/** ç¼–辑头像 */
const editCropper = () => {
    open.value = true;
}
  open.value = true;
};
/** æ‰“开弹出层结束时的回调 */
const modalOpened = () => {
    visible.value = true;
}
  visible.value = true;
};
/** è¦†ç›–默认上传行为 */
const requestUpload = (): any => {}
const requestUpload = (): any => {
};
/** å‘左旋转 */
const rotateLeft = () => {
    cropper.value.rotateLeft();
}
  cropper.value.rotateLeft();
};
/** å‘右旋转 */
const rotateRight = () => {
    cropper.value.rotateRight();
}
  cropper.value.rotateRight();
};
/** å›¾ç‰‡ç¼©æ”¾ */
const changeScale = (num: number) => {
    num = num || 1;
    cropper.value.changeScale(num);
}
  num = num || 1;
  cropper.value.changeScale(num);
};
/** ä¸Šä¼ é¢„处理 */
const beforeUpload = (file: any) => {
    if (file.type.indexOf("image/") == -1) {
        proxy?.$modal.msgError("文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。");
    } else {
        const reader = new FileReader();
        reader.readAsDataURL(file);
        reader.onload = () => {
            options.img = reader.result;
            options.fileName = file.name;
        };
    }
}
  if (file.type.indexOf("image/") == -1) {
    proxy?.$modal.msgError("文件格式错误,请上传图片类型,如:JPG,PNG后缀的文件。");
  } else {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () => {
      options.img = reader.result;
      options.fileName = file.name;
    };
  }
};
/** ä¸Šä¼ å›¾ç‰‡ */
const uploadImg = async () => {
    cropper.value.getCropBlob(async (data: any) => {
        let formData = new FormData();
        formData.append("avatarfile", data, options.fileName);
        const res = await uploadAvatar(formData);
        open.value = false;
        options.img = res.data.imgUrl;
        userStore.avatar = options.img as string;
        proxy?.$modal.msgSuccess("修改成功");
        visible.value = false;
    });
}
  cropper.value.getCropBlob(async (data: any) => {
    let formData = new FormData();
    formData.append("avatarfile", data, options.fileName);
    const res = await uploadAvatar(formData);
    open.value = false;
    options.img = res.data.imgUrl;
    userStore.avatar = options.img as string
    proxy?.$modal.msgSuccess("修改成功");
    visible.value = false;
  });
};
/** å®žæ—¶é¢„览 */
const realTime = (data: any) => {
    options.previews = data;
}
  options.previews = data;
};
/** å…³é—­çª—口 */
const closeDialog = () => {
    options.img = userStore.avatar;
    options.visible = false;
}
  options.img = userStore.avatar;
  options.visible = false;
};
</script>
<style lang="scss" scoped>
src/views/system/user/profile/userInfo.vue
@@ -24,40 +24,42 @@
<script setup lang="ts">
import { updateUserProfile } from "@/api/system/user";
import { FormRules } from "element-plus";
import { ComponentInternalInstance } from "vue";
import { PropType } from "vue";
import { ElForm } from "element-plus";
const props = defineProps({
    user: {
        type: Object as PropType<any>,
    }
  user: {
    type: Object as PropType<any>,
    required: true
  }
});
const userForm = computed(() => props.user);
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userRef = ref(ElForm);
const rules = ref<FormRules>({
    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 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" }]
});
/** æäº¤æŒ‰é’® */
const submit = () => {
    userRef.value.validate(async (valid: boolean) => {
        if (valid) {
            await updateUserProfile(props.user)
            proxy?.$modal.msgSuccess("修改成功");
        }
    });
  userRef.value?.validate(async (valid: boolean) => {
    if (valid) {
      await updateUserProfile(props.user);
      proxy?.$modal.msgSuccess("修改成功");
    }
  });
};
/** å…³é—­æŒ‰é’® */
const close = () => {
    proxy?.$tab.closePage();
  proxy?.$tab.closePage();
};
</script>
src/views/tool/gen/basicInfoForm.vue
@@ -31,24 +31,19 @@
</template>
<script setup lang="ts">
import { PropType } from 'vue';
import { propTypes } from "@/utils/propTypes";
const prop = defineProps({
    info: {
        type: Object as PropType<any>,
        default: () => {
            return {};
        }
    }
  info: propTypes.any.def({})
});
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
@@ -117,9 +117,8 @@
import { DbColumnVO, DbTableVO } from '@/api/tool/gen/types';
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 { ComponentInternalInstance } from "vue";
import BasicInfoForm from './basicInfoForm.vue';
import GenInfoForm from "./genInfoForm.vue";
const route = useRoute();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -131,13 +130,13 @@
const dictOptions = ref<DictTypeVO[]>([]);
const info = ref<Partial<DbTableVO>>({});
const basicInfo = ref(basicInfoForm);
const genInfo = ref(genInfoForm);
const basicInfo = ref<InstanceType<typeof BasicInfoForm>>();
const genInfo = ref<InstanceType<typeof GenInfoForm>>();
/** æäº¤æŒ‰é’® */
const submitForm = () => {
  const basicForm = basicInfo.value.$refs.basicInfoForm;
  const genForm = genInfo.value.$refs.genInfoForm;
  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);
@@ -168,7 +167,7 @@
  });
}
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);
}
src/views/tool/gen/genInfoForm.vue
@@ -223,7 +223,7 @@
<script setup lang="ts">
import { listMenu } from '@/api/system/menu';
import { ComponentInternalInstance, PropType } from 'vue';
import { propTypes } from "@/utils/propTypes";
interface MenuOptionsType {
  menuId: number | string;
@@ -236,14 +236,8 @@
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const props = defineProps({
  info: {
    type: Object as PropType<any>,
    default: null
  },
  tables: {
    type: Array as PropType<any[]>,
    default: null
  }
  info: propTypes.any.def(null),
  tables: propTypes.any.def(null)
});
const infoForm = computed(() => props.info);
@@ -252,11 +246,11 @@
// è¡¨å•校验
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 = "";
@@ -268,7 +262,7 @@
  }
}
const setSubTableColumns = (value: string) => {
  table.value.forEach(item => {
  table.value.forEach((item: any) => {
    const name = item.tableName;
    if (value === name) {
      subColumns.value = item.columns;
src/views/tool/gen/importTable.vue
@@ -3,11 +3,11 @@
  <el-dialog title="导入表" v-model="visible" width="1100px" top="5vh" append-to-body>
    <el-form :model="queryParams" ref="queryFormRef" :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>
            </el-select>
          </el-form-item>
          <el-form-item label="表名称" prop="tableName">
        <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>
        </el-select>
      </el-form-item>
      <el-form-item label="表名称" prop="tableName">
        <el-input v-model="queryParams.tableName" placeholder="请输入表名称" clearable @keyup.enter="handleQuery" />
      </el-form-item>
      <el-form-item label="表描述" prop="tableComment">
@@ -26,7 +26,7 @@
        <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" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
    </el-row>
    <template #footer>
      <div class="dialog-footer">
@@ -40,8 +40,6 @@
<script setup lang="ts">
import { listDbTable, importTable, getDataNames } from '@/api/tool/gen';
import { DbTableQuery, DbTableVO } from '@/api/tool/gen/types';
import { ComponentInternalInstance } from 'vue';
import { ElTable, ElForm } from 'element-plus';
const total = ref(0);
const visible = ref(false);
@@ -49,15 +47,15 @@
const dbTableList = ref<Array<DbTableVO>>([]);
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const tableRef = ref(ElTable);
const queryFormRef = ref(ElForm);
const tableRef = ref<ElTableInstance>();
const queryFormRef = ref<ElFormInstance>();
const queryParams = reactive<DbTableQuery>({
    pageNum: 1,
    pageSize: 10,
    dataName: '',
    tableName: '',
    tableComment: ''
  pageNum: 1,
  pageSize: 10,
  dataName: '',
  tableName: '',
  tableComment: ''
});
const dataNameList = ref<Array<string>>([]);
@@ -65,52 +63,53 @@
/** æŸ¥è¯¢å‚数列表 */
const show = (dataName: string) => {
    getDataNameList();
    if(dataName){
      queryParams.dataName = dataName;
    } else {
      queryParams.dataName = 'master';
    }
    getList();
    visible.value = true;
  getDataNameList();
  if (dataName) {
    queryParams.dataName = dataName;
  } else {
    queryParams.dataName = 'master';
  }
  getList();
  visible.value = true;
}
/** å•击选择行 */
const clickRow = (row: DbTableVO) => {
    tableRef.value.toggleRowSelection(row);
  // ele bug
  tableRef.value?.toggleRowSelection(row);
}
/** å¤šé€‰æ¡†é€‰ä¸­æ•°æ® */
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 res = await listDbTable(queryParams);
  dbTableList.value = res.rows;
  total.value = res.total;
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    queryParams.pageNum = 1;
    getList();
  queryParams.pageNum = 1;
  getList();
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
    queryFormRef.value.resetFields();
    handleQuery();
  queryFormRef.value?.resetFields();
  handleQuery();
}
/** å¯¼å…¥æŒ‰é’®æ“ä½œ */
const handleImportTable = async () => {
    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");
    }
  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");
  }
}
/** æŸ¥è¯¢å¤šæ•°æ®æºåç§° */
const getDataNameList = async () => {
@@ -119,6 +118,6 @@
}
defineExpose({
    show,
  show,
});
</script>
src/views/tool/gen/index.vue
@@ -1,39 +1,41 @@
<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="68px">
          <el-form-item label="数据源" prop="dataName">
            <el-select v-model="queryParams.dataName" filterable clearable placeholder="请选择/输入数据源名称" style="width: 200px">
              <el-option key="" label="全部" value="" />
              <el-option v-for="item in dataNameList" :key="item" :label="item" :value="item"> </el-option>
            </el-select>
          </el-form-item>
          <el-form-item label="表名称" prop="tableName">
            <el-input v-model="queryParams.tableName" placeholder="请输入表名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="表描述" prop="tableComment">
            <el-input v-model="queryParams.tableComment" placeholder="请输入表描述" clearable style="width: 200px" @keyup.enter="handleQuery" />
          </el-form-item>
          <el-form-item label="创建时间" style="width: 308px">
            <el-date-picker
              v-model="dateRange"
              value-format="YYYY-MM-DD"
              type="daterange"
              range-separator="-"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
            ></el-date-picker>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
          </el-form-item>
        </el-form>
      <div class="mb-[10px]" v-show="showSearch">
        <el-card shadow="hover">
          <el-form :model="queryParams" ref="queryFormRef" :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="" />
                <el-option v-for="item in dataNameList" :key="item" :label="item" :value="item"> </el-option>
              </el-select>
            </el-form-item>
            <el-form-item label="表名称" prop="tableName">
              <el-input v-model="queryParams.tableName" placeholder="请输入表名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="表描述" prop="tableComment">
              <el-input v-model="queryParams.tableComment" placeholder="请输入表描述" clearable style="width: 200px" @keyup.enter="handleQuery" />
            </el-form-item>
            <el-form-item label="创建时间" style="width: 308px">
              <el-date-picker
                v-model="dateRange"
                value-format="YYYY-MM-DD"
                type="daterange"
                range-separator="-"
                start-placeholder="开始日期"
                end-placeholder="结束日期"
              ></el-date-picker>
            </el-form-item>
            <el-form-item>
              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
            </el-form-item>
          </el-form>
        </el-card>
      </div>
    </transition>
    <el-card shadow="never">
    <el-card shadow="hover">
      <template #header>
        <el-row :gutter="10" class="mb8">
          <el-col :span="1.5">
@@ -99,9 +101,9 @@
          :name="(key as any).substring((key as any).lastIndexOf('/') + 1, (key as any).indexOf('.vm'))"
          :key="value"
        >
          <el-link :underline="false" icon="DocumentCopy" v-copyText="value" v-copyText:callback="copyTextSuccess" style="float:right"
            >&nbsp;复制</el-link
          >
          <el-link :underline="false" icon="DocumentCopy" v-copyText="value" v-copyText:callback="copyTextSuccess" style="float:right">
            &nbsp;复制
          </el-link>
          <pre>{{ value }}</pre>
        </el-tab-pane>
      </el-tabs>
@@ -114,9 +116,7 @@
import { listTable, previewTable, delTable, genCode, synchDb, getDataNames } from '@/api/tool/gen';
import { TableQuery, TableVO } from '@/api/tool/gen/types';
import router from '@/router';
import importTable from './importTable.vue';
import { ComponentInternalInstance } from 'vue';
import { ElForm, DateModelType } from 'element-plus';
import ImportTable from './importTable.vue';
const route = useRoute();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -132,35 +132,35 @@
const uniqueId = ref("");
const dataNameList = ref<Array<string>>([]);
const queryFormRef = ref(ElForm);
const importRef = ref(importTable);
const queryFormRef = ref<ElFormInstance>();
const importRef = ref<InstanceType<typeof ImportTable>>();
const queryParams = ref<TableQuery>({
    pageNum: 1,
    pageSize: 10,
    tableName: '',
    tableComment: '',
    dataName: ""
  pageNum: 1,
  pageSize: 10,
  tableName: '',
  tableComment: '',
  dataName: ""
})
const preview = ref <any>({
    data: {},
    activeName: 'domain.java'
const preview = ref<any>({
  data: {},
  activeName: 'domain.java'
})
const dialog = reactive<DialogOption>({
    visible: false,
    title: '代码预览'
  visible: false,
  title: '代码预览'
});
onActivated(() => {
    const time = route.query.t;
    if (time != null && time != uniqueId.value) {
        uniqueId.value = time as string;
        queryParams.value.pageNum = Number(route.query.pageNum);
        dateRange.value = ['', ''];
        queryFormRef.value.resetFields();
        getList();
    }
  const time = route.query.t;
  if (time != null && time != uniqueId.value) {
    uniqueId.value = time as string;
    queryParams.value.pageNum = Number(route.query.pageNum);
    dateRange.value = ['', ''];
    queryFormRef.value?.resetFields();
    getList();
  }
})
/** æŸ¥è¯¢å¤šæ•°æ®æºåç§° */
@@ -171,81 +171,81 @@
/** æŸ¥è¯¢è¡¨é›†åˆ */
const getList = async () => {
    loading.value = true;
    const res = await listTable(proxy?.addDateRange(queryParams.value, dateRange.value));
    tableList.value = res.rows;
    total.value = res.total;
    loading.value = false;
  loading.value = true;
  const res = await listTable(proxy?.addDateRange(queryParams.value, dateRange.value));
  tableList.value = res.rows;
  total.value = res.total;
  loading.value = false;
}
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    queryParams.value.pageNum = 1;
    getList();
  queryParams.value.pageNum = 1;
  getList();
}
/** ç”Ÿæˆä»£ç æ“ä½œ */
const handleGenTable = async (row?: TableVO) => {
    const tbIds = row?.tableId || ids.value;
    if (tbIds == "") {
        proxy?.$modal.msgError('请选择要生成的数据');
        return;
    }
    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 tbIds = row?.tableId || ids.value;
  if (tbIds == "") {
    proxy?.$modal.msgError('请选择要生成的数据');
    return;
  }
  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 tableId = row.tableId;
  await proxy?.$modal.confirm('确认要强制同步"' + row.tableName + '"表结构吗?');
  await synchDb(tableId);
  proxy?.$modal.msgSuccess('同步成功');
}
/** æ‰“开导入表弹窗 */
const openImportTable = () => {
    importRef.value.show(queryParams.value.dataName);
  importRef.value?.show(queryParams.value.dataName);
}
/** é‡ç½®æŒ‰é’®æ“ä½œ */
const resetQuery = () => {
    dateRange.value = ['', ''];
    queryFormRef.value.resetFields();
    handleQuery();
  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 res = await previewTable(row.tableId);
  preview.value.data = res.data;
  dialog.visible = true;
  preview.value.activeName = 'domain.java';
}
/** å¤åˆ¶ä»£ç æˆåŠŸ */
const copyTextSuccess = () => {
    proxy?.$modal.msgSuccess('复制成功');
  proxy?.$modal.msgSuccess('复制成功');
}
// å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
const handleSelectionChange = (selection: TableVO[]) => {
    ids.value = selection.map(item => item.tableId);
    single.value = selection.length != 1;
    multiple.value = !selection.length;
  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 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;
    await proxy?.$modal.confirm('是否确认删除表编号为"' + tableIds + '"的数据项?');
    await delTable(tableIds);
    getList();
    proxy?.$modal.msgSuccess('删除成功');
  const tableIds = row?.tableId || ids.value;
  await proxy?.$modal.confirm('是否确认删除表编号为"' + tableIds + '"的数据项?');
  await delTable(tableIds);
  getList();
  proxy?.$modal.msgSuccess('删除成功');
}
onMounted(() => {
    getList();
    getDataNameList();
  getList();
  getDataNameList();
})
</script>