houzhongjian
2024-09-13 39248bc48bd1c2b66e18337dadd70d50b2bfaae6
框架及页面bug修改
已修改20个文件
已删除26个文件
已添加9个文件
4228 ■■■■ 文件已修改
src/api/infra/demo/demo01/index.ts 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/infra/demo/demo02/index.ts 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/infra/demo/demo03/erp/index.ts 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/infra/demo/demo03/inner/index.ts 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/infra/demo/demo03/normal/index.ts 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/infra/storage/index.ts 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/infra/storage/types.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/login/index.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/app/index.ts 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/appgroup/index.ts 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/appmenu/index.ts 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/menu/index.ts 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/oauth2/token.ts 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/system/tenant/index.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/FormCreate/src/components/useApiSelect.tsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/bpmnProcessDesigner/package/utils.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/config/axios/index.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Logo/src/Logo.vue 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/permission.ts 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/plugins/unocss/index.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/modules/remaining.ts 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/constants.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/dict.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/routerHelper.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Login/components/LoginForm.vue 104 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/bpm/processInstance/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/bpm/task/copy/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/data/job/ScheduleJobFormLog.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/demo/demo01/Demo01ContactForm.vue 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/demo/demo01/index.vue 212 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/demo/demo02/Demo02CategoryForm.vue 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/demo/demo02/index.vue 205 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/demo/demo03/erp/Demo03StudentForm.vue 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/demo/demo03/erp/components/Demo03CourseForm.vue 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/demo/demo03/erp/components/Demo03CourseList.vue 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/demo/demo03/erp/components/Demo03GradeForm.vue 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/demo/demo03/erp/components/Demo03GradeList.vue 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/demo/demo03/erp/index.vue 246 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/demo/demo03/inner/Demo03StudentForm.vue 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/demo/demo03/inner/components/Demo03CourseForm.vue 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/demo/demo03/inner/components/Demo03CourseList.vue 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/demo/demo03/inner/components/Demo03GradeForm.vue 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/demo/demo03/inner/components/Demo03GradeList.vue 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/demo/demo03/inner/index.vue 227 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/demo/demo03/normal/Demo03StudentForm.vue 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/demo/demo03/normal/components/Demo03CourseForm.vue 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/demo/demo03/normal/components/Demo03GradeForm.vue 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/demo/demo03/normal/index.vue 212 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/server/index.vue 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/infra/storage/index_rec.vue 161 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/appgroup/AppGroupForm.vue 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/appgroup/index.vue 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/appmenu/AppMenuForm.vue 256 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/appmenu/index.vue 203 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/infra/demo/demo01/index.ts
文件已删除
src/api/infra/demo/demo02/index.ts
文件已删除
src/api/infra/demo/demo03/erp/index.ts
文件已删除
src/api/infra/demo/demo03/inner/index.ts
文件已删除
src/api/infra/demo/demo03/normal/index.ts
文件已删除
src/api/infra/storage/index.ts
对比新文件
@@ -0,0 +1,15 @@
import request from '@/config/axios'
/**
 * 获取磁盘信息
 */
export const getDiskInfo = () => {
  return request.get({ url: '/infra/actuator/disk/info' })
}
/**
 * 获取内存信息
 */
export const getMemoryInfo = () => {
  return request.get({ url: '/infra/actuator/memory/info' })
}
src/api/infra/storage/types.ts
对比新文件
@@ -0,0 +1,6 @@
export interface StorageMonitorInfoVO {
  name: string
  max: number
  rest: number
  restPPT: number
}
src/api/login/index.ts
@@ -42,6 +42,11 @@
  return request.get({ url: '/system/auth/get-permission-info' })
}
// 获取用应用户权限信息
export const getUserAppInfo = (id: number) => {
  return request.get({ url: '/system/auth/get-app-permission-info?id=' + id })
}
//获取登录验证码
export const sendSmsCode = (data: SmsCodeVO) => {
  return request.post({ url: '/system/auth/send-sms-code', data })
src/api/system/app/index.ts
@@ -19,11 +19,16 @@
  createTime: Date
}
// 查询列表
// 查询分页列表
export const getAppPage = (params: PageParam) => {
  return request.get({ url: '/system/app/page', params })
}
// 查询列表
export const getAppList = () => {
  return request.get({ url: '/system/app/getAppList' })
}
// 获得
export const getApp = (id: number) => {
  return request.get({ url: '/system/app/get?id=' + id })
src/api/system/appgroup/index.ts
对比新文件
@@ -0,0 +1,41 @@
import request from '@/config/axios'
export interface AppGroupVO {
  id: number
  node: string
  name: string
  icon: string
  sort: number
  remark: string
  createTime: Date
}
// 查询分页列表
export const getAppGroupPage = (params: PageParam) => {
  return request.get({ url: '/system/app-group/page', params })
}
// 查询列表
export const getAppGroupList = () => {
  return request.get({ url: '/system/app-group/getAppGroupList' })
}
// 获得
export const getAppGroup = (id: number) => {
  return request.get({ url: '/system/app-group/get?id=' + id })
}
// 新增
export const createAppGroup = (data: AppGroupVO) => {
  return request.post({ url: '/system/app-group/create', data })
}
// 修改
export const updateAppGroup = (data: AppGroupVO) => {
  return request.put({ url: '/system/app-group/update', data })
}
// 删除
export const deleteAppGroup = (id: number) => {
  return request.delete({ url: '/system/app-group/delete?id=' + id })
}
src/api/system/appmenu/index.ts
对比新文件
@@ -0,0 +1,49 @@
import request from '@/config/axios'
export interface AppMenuVO {
  id: number
  name: string
  permission: string
  type: number
  sort: number
  parentId: number
  path: string
  icon: string
  component: string
  componentName?: string
  status: number
  visible: boolean
  keepAlive: boolean
  alwaysShow?: boolean
  createTime: Date
}
// 查询菜单(精简)列表
export const getSimpleMenusList = () => {
  return request.get({ url: '/system/app-menu/simple-list' })
}
// 查询菜单列表
export const getMenuList = (params) => {
  return request.get({ url: '/system/app-menu/list', params })
}
// 获取菜单详情
export const getMenu = (id: number) => {
  return request.get({ url: '/system/app-menu/get?id=' + id })
}
// 新增菜单
export const createMenu = (data: AppMenuVO) => {
  return request.post({ url: '/system/app-menu/create', data })
}
// 修改菜单
export const updateMenu = (data: AppMenuVO) => {
  return request.put({ url: '/system/app-menu/update', data })
}
// 删除菜单
export const deleteMenu = (id: number) => {
  return request.delete({ url: '/system/app-menu/delete?id=' + id })
}
src/api/system/menu/index.ts
@@ -16,6 +16,7 @@
  keepAlive: boolean
  alwaysShow?: boolean
  createTime: Date
  appId: number
}
// 查询菜单(精简)列表
@@ -23,9 +24,19 @@
  return request.get({ url: '/system/menu/simple-list' })
}
// 查询应用菜单(精简)列表
export const getSimpleAppMenusList = () => {
  return request.get({ url: '/system/menu/simple-app-menus' })
}
// 查询菜单列表
export const getMenuList = (params) => {
  return request.get({ url: '/system/menu/list', params })
}
// 查询应用菜单列表
export const getAppMenuList = (params) => {
  return request.get({ url: '/system/menu/app-menu-list', params })
}
// 获取菜单详情
@@ -33,9 +44,19 @@
  return request.get({ url: '/system/menu/get?id=' + id })
}
// 获取应用菜单详情
export const getAppMenu = (id: number) => {
  return request.get({ url: '/system/menu/getAppMenu?id=' + id })
}
// 新增菜单
export const createMenu = (data: MenuVO) => {
  return request.post({ url: '/system/menu/create', data })
}
// 新增应用菜单
export const createAppMenu = (data: MenuVO) => {
  return request.post({ url: '/system/menu/createAppMenu', data })
}
// 修改菜单
@@ -43,7 +64,17 @@
  return request.put({ url: '/system/menu/update', data })
}
// 修改应用菜单
export const updateAppMenu = (data: MenuVO) => {
  return request.put({ url: '/system/menu/updateAppMenu', data })
}
// 删除菜单
export const deleteMenu = (id: number) => {
  return request.delete({ url: '/system/menu/delete?id=' + id })
}
// 删除应用菜单
export const deleteAppMenu = (id: number) => {
  return request.delete({ url: '/system/menu/deleteAppMenu?id=' + id })
}
src/api/system/oauth2/token.ts
@@ -1,14 +1,12 @@
import request from '@/config/axios'
export interface OAuth2TokenVO {
  id: number
  accessToken: string
  refreshToken: string
  userId: number
  userType: number
  clientId: string
  createTime: Date
  expiresTime: Date
  grantType: string
  scope: string
  refreshToken: any
  username: string
  password: string
  redirectUri: string
}
// 查询 token列表
@@ -16,6 +14,11 @@
  return request.get({ url: '/system/oauth2-token/page', params })
}
// 单点登录授权
export const OAuth2Login = (params: OAuth2TokenVO) => {
  return request.post({ url: '/system/oauth2/token', data: params })
}
// 删除 token
export const deleteAccessToken = (accessToken: string) => {
  return request.delete({ url: '/system/oauth2-token/delete?accessToken=' + accessToken })
src/api/system/tenant/index.ts
@@ -37,6 +37,11 @@
  return request.get({ url: '/system/tenant/page', params })
}
// 查询租户列表
export const getSimpleTenant = () => {
  return request.get({ url: '/system/tenant/simple-list' })
}
// 查询租户详情
export const getTenant = (id: number) => {
  return request.get({ url: '/system/tenant/get?id=' + id })
src/components/FormCreate/src/components/useApiSelect.tsx
@@ -185,7 +185,7 @@
            </el-select>
          )
        }
        debugger
        // debugger
        return (
          <el-select
            class="w-1/1"
src/components/bpmnProcessDesigner/package/penal/listeners/ElementListeners.vue
@@ -370,7 +370,7 @@
}
// 移除监听器
const removeListener = (index) => {
  debugger
  // debugger
  ElMessageBox.confirm('确认移除该监听器吗?', '提示', {
    confirmButtonText: '确 认',
    cancelButtonText: '取 消'
src/components/bpmnProcessDesigner/package/utils.ts
@@ -2,7 +2,7 @@
const bpmnInstances = () => (window as any)?.bpmnInstances
// 创建监听器实例
export function createListenerObject(options, isTask, prefix) {
  debugger
  // debugger
  const listenerObj = Object.create(null)
  listenerObj.event = options.event
  isTask && (listenerObj.id = options.id) // 任务监听器特有的 id 字段
src/config/axios/index.ts
@@ -14,7 +14,7 @@
    ...config,
    responseType: responseType,
    headers: {
      'Content-Type': headersType || default_headers
      'Content-Type': headersType || default_headers,
    }
  })
}
src/layout/components/Logo/src/Logo.vue
@@ -2,6 +2,7 @@
import { computed, onMounted, ref, unref, watch } from 'vue'
import { useAppStore } from '@/store/modules/app'
import { useDesign } from '@/hooks/web/useDesign'
import * as authUtil from "@/utils/auth";
defineOptions({ name: 'Logo' })
@@ -13,11 +14,22 @@
const show = ref(true)
const homePath = ref('/index')
const title = computed(() => appStore.getTitle)
const layout = computed(() => appStore.getLayout)
const collapse = computed(() => appStore.getCollapse)
let tenantId = authUtil.getTenantId()
console.log(tenantId)
if (tenantId && tenantId === 1) {
  homePath.value = '/index'
} else {
  homePath.value = '/home2'
}
console.log(homePath.value)
onMounted(() => {
  if (unref(collapse)) show.value = false
@@ -64,7 +76,7 @@
        layout !== 'classic' ? `${prefixCls}__Top` : '',
        'flex !h-[var(--logo-height)] items-center cursor-pointer pl-8px relative decoration-none overflow-hidden'
      ]"
      to="/"
      :to="homePath"
    >
      <img
        class="h-[calc(var(--logo-height)-10px)] w-[calc(var(--logo-height)-10px)]"
src/permission.ts
@@ -73,7 +73,8 @@
      }
      if (!userStore.getIsSetUser) {
        isRelogin.show = true
        await userStore.setUserInfoAction()
        await userStore.
        setUserInfoAction()
        isRelogin.show = false
        // 后端过滤菜单
        await permissionStore.generateRoutes()
src/plugins/unocss/index.ts
@@ -1 +1,2 @@
import 'virtual:uno.css'
import 'uno.css'
src/router/modules/remaining.ts
@@ -1,4 +1,4 @@
import { Layout } from '@/utils/routerHelper'
import {Layout} from '@/utils/routerHelper'
const { t } = useI18n()
/**
@@ -51,11 +51,22 @@
    }
  },
  {
    path: '/home2',
    component: () => import('@/views/Home/Index2.vue'),
    name: 'Home2',
    meta: {
      hidden: true,
      noTagsView: true
    },
  },
  {
    path: '/',
    component: Layout,
    redirect: '/index',
    name: 'Home',
    meta: {},
    meta: {
      hidden: true,
      noTagsView: true
    },
    children: [
      {
        path: 'index',
src/utils/constants.ts
@@ -28,6 +28,15 @@
}
/**
 * 应用菜单的类型枚举
 */
export const SystemAppMenuTypeEnum = {
  DIR: 1, // 应用
  MENU: 2, // 菜单
  BUTTON: 3 // 按钮
}
/**
 * 角色的类型枚举
 */
export const SystemRoleTypeEnum = {
src/utils/dict.ts
@@ -116,6 +116,8 @@
  // ========== SYSTEM 模块 ==========
  SYSTEM_USER_SEX = 'system_user_sex',
  SYSTEM_MENU_TYPE = 'system_menu_type',
  SYSTEM_APP_MENU_TYPE = 'system_app_menu_type',
  SYSTEM_APP_TYPE = 'system_app_type',
  SYSTEM_ROLE_TYPE = 'system_role_type',
  SYSTEM_DATA_SCOPE = 'system_data_scope',
  SYSTEM_NOTICE_TYPE = 'system_notice_type',
src/utils/routerHelper.ts
@@ -21,6 +21,7 @@
/* Layout */
export const Layout = () => import('@/layout/Layout.vue')
export const getParentLayout = () => {
  return () =>
    new Promise((resolve) => {
src/views/Login/components/LoginForm.vue
@@ -82,49 +82,6 @@
        mode="pop"
        @success="handleLogin"
      />
      <!--<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
        <el-form-item>
          <el-row :gutter="5" justify="space-between" style="width: 100%">
            <el-col :span="8">
              <XButton
                :title="t('login.btnMobile')"
                class="w-[100%]"
                @click="setLoginState(LoginStateEnum.MOBILE)"
              />
            </el-col>
            <el-col :span="8">
              <XButton
                :title="t('login.btnQRCode')"
                class="w-[100%]"
                @click="setLoginState(LoginStateEnum.QR_CODE)"
              />
            </el-col>
            <el-col :span="8">
              <XButton
                :title="t('login.btnRegister')"
                class="w-[100%]"
                @click="setLoginState(LoginStateEnum.REGISTER)"
              />
            </el-col>
          </el-row>
        </el-form-item>
      </el-col>-->
      <!--<el-divider content-position="center">{{ t('login.otherLogin') }}</el-divider>
      <el-col :span="24" style="padding-right: 10px; padding-left: 10px">
        <el-form-item>
          <div class="w-[100%] flex justify-between">
            <Icon
              v-for="(item, key) in socialList"
              :key="key"
              :icon="item.icon"
              :size="30"
              class="anticon cursor-pointer"
              color="#999"
              @click="doSocialLogin(item.type)"
            />
          </div>
        </el-form-item>
      </el-col>-->
    </el-row>
  </el-form>
</template>
@@ -176,13 +133,6 @@
    rememberMe: true // 默认记录我。如果不需要,可手动修改
  }
})
const socialList = [
  { icon: 'ant-design:wechat-filled', type: 30 },
  { icon: 'ant-design:dingtalk-circle-filled', type: 20 },
  { icon: 'ant-design:github-filled', type: 0 },
  { icon: 'ant-design:alipay-circle-filled', type: 0 }
]
// 获取验证码
const getCode = async () => {
@@ -253,54 +203,24 @@
    if (!redirect.value) {
      redirect.value = '/'
    }
    // 判断是否为SSO登录
    if (redirect.value.indexOf('sso') !== -1) {
      window.location.href = window.location.href.replace('/login?redirect=', '')
    } else {
      push({ path: redirect.value || permissionStore.addRouters[0].path })
    }
    let tenantId = authUtil.getTenantId()
    // if(tenantId != 1) {
    //   //只要不是系统租户,登录成功跳转到home2页面
    //   window.location.href = '/home2'
    // } else {
      // 判断是否为SSO登录
      if (redirect.value.indexOf('sso') !== -1) {
        window.location.href = window.location.href.replace('/login?redirect=', '')
      } else {
        push({ path: redirect.value || permissionStore.addRouters[0].path })
      }
    // }
  } finally {
    loginLoading.value = false
    loading.value.close()
  }
}
// 社交登录
const doSocialLogin = async (type: number) => {
  if (type === 0) {
    message.error('此方式未配置')
  } else {
    loginLoading.value = true
    if (loginData.tenantEnable === 'true') {
      // 尝试先通过 tenantName 获取租户
      await getTenantId()
      // 如果获取不到,则需要弹出提示,进行处理
      if (!authUtil.getTenantId()) {
        try {
          const data = await message.prompt('请输入租户名称', t('common.reminder'))
          if (data?.action !== 'confirm') throw 'cancel'
          const res = await LoginApi.getTenantIdByName(data.value)
          authUtil.setTenantId(res)
        } catch (error) {
          if (error === 'cancel') return
        } finally {
          loginLoading.value = false
        }
      }
    }
    // 计算 redirectUri
    // tricky: type、redirect需要先encode一次,否则钉钉回调会丢失。
    // 配合 Login/SocialLogin.vue#getUrlValue() 使用
    const redirectUri =
      location.origin +
      '/social-login?' +
      encodeURIComponent(`type=${type}&redirect=${redirect.value || '/'}`)
    // 进行跳转
    const res = await LoginApi.socialAuthRedirect(type, encodeURIComponent(redirectUri))
    window.location.href = res
  }
}
watch(
  () => currentRoute.value,
  (route: RouteLocationNormalizedLoaded) => {
src/views/bpm/processInstance/index.vue
@@ -221,7 +221,6 @@
    const processDefinitionDetail = await DefinitionApi.getProcessDefinition(
      row.processDefinitionId
    )
    debugger
    if (processDefinitionDetail.formType === 20) {
      message.error('重新发起流程失败,原因:该流程使用业务表单,不支持重新发起')
      return
src/views/bpm/task/copy/index.vue
@@ -1,3 +1,5 @@
<!-- 工作流 - 抄送我的流程 -->
<template>
  <ContentWrap>
    <!-- 搜索工作栏 -->
    <el-form ref="queryFormRef" :inline="true" class="-mb-15px" label-width="68px">
src/views/data/job/ScheduleJobFormLog.vue
src/views/infra/demo/demo01/Demo01ContactForm.vue
文件已删除
src/views/infra/demo/demo01/index.vue
文件已删除
src/views/infra/demo/demo02/Demo02CategoryForm.vue
文件已删除
src/views/infra/demo/demo02/index.vue
文件已删除
src/views/infra/demo/demo03/erp/Demo03StudentForm.vue
文件已删除
src/views/infra/demo/demo03/erp/components/Demo03CourseForm.vue
文件已删除
src/views/infra/demo/demo03/erp/components/Demo03CourseList.vue
文件已删除
src/views/infra/demo/demo03/erp/components/Demo03GradeForm.vue
文件已删除
src/views/infra/demo/demo03/erp/components/Demo03GradeList.vue
文件已删除
src/views/infra/demo/demo03/erp/index.vue
文件已删除
src/views/infra/demo/demo03/inner/Demo03StudentForm.vue
文件已删除
src/views/infra/demo/demo03/inner/components/Demo03CourseForm.vue
文件已删除
src/views/infra/demo/demo03/inner/components/Demo03CourseList.vue
文件已删除
src/views/infra/demo/demo03/inner/components/Demo03GradeForm.vue
文件已删除
src/views/infra/demo/demo03/inner/components/Demo03GradeList.vue
文件已删除
src/views/infra/demo/demo03/inner/index.vue
文件已删除
src/views/infra/demo/demo03/normal/Demo03StudentForm.vue
文件已删除
src/views/infra/demo/demo03/normal/components/Demo03CourseForm.vue
文件已删除
src/views/infra/demo/demo03/normal/components/Demo03GradeForm.vue
文件已删除
src/views/infra/demo/demo03/normal/index.vue
文件已删除
src/views/infra/server/index.vue
@@ -14,9 +14,6 @@
/** 初始化 */
onMounted(async () => {
  try {
    // 友情提示:如果访问出现 404 问题:
    // 1)boot 参考 https://doc.iocoder.cn/server-monitor/ 解决;
    // 2)cloud 参考 https://cloud.iocoder.cn/server-monitor/ 解决
    const data = await ConfigApi.getConfigKey('url.spring-boot-admin')
    if (data && data.length > 0) {
      src.value = data
src/views/infra/storage/index_rec.vue
对比新文件
@@ -0,0 +1,161 @@
<template>
  <el-scrollbar height="calc(100vh - 88px - 40px - 50px)">
    <el-row>
      <!-- 磁盘使用量统计 -->
        <el-col :span="12" class="mt-3">
          <el-card class="ml-3" :gutter="12" shadow="hover">
<!--            <div ref="chartRef"  style="width: 100%; height: 90%"></div>-->
            <Echart :options="usedDiskEchartChika" :height="420" />
<!--            <Echart :options="usedDiskEchartChika" :height="420" />-->
          </el-card>
        </el-col>
    </el-row>
  </el-scrollbar>
</template>
<script lang="ts" setup>
import { ref, onMounted } from "vue";
import * as StorageApi from '@/api/infra/storage'
import { StorageMonitorInfoVO } from '@/api/infra/storage/types'
const disks = ref<StorageMonitorInfoVO>()
const disk = ref<StorageMonitorInfoVO>()
// 基本信息
const readDiskInfo = async () => {
  const data = await StorageApi.getDiskInfo()
  disks.value = data
  disk.value = data[0]
}
// 内存使用情况
const usedDiskEchartChika = reactive<any>({
  title: {
    // 仪表盘标题。
    text: '磁盘使用情况',
    left: 'center',
    show: true, // 是否显示标题,默认 true。
    offsetCenter: [0, '20%'], //相对于仪表盘中心的偏移位置,数组第一项是水平方向的偏移,第二项是垂直方向的偏移。可以是绝对的数值,也可以是相对于仪表盘半径的百分比。
    color: 'yellow', // 文字的颜色,默认 #333。
    fontSize: 20 // 文字的字体大小,默认 15。
  },
  toolbox: {
    show: false,
    feature: {
      restore: { show: true },
      saveAsImage: { show: true }
    }
  },
  series: [
    {
      name: '峰值',
      type: 'gauge',
      min: 0,
      max: 500,
      splitNumber: 10,
      //这是指针的颜色
      color: '#F5C74E',
      radius: '85%',
      center: ['50%', '50%'],
      startAngle: 225,
      endAngle: -45,
      axisLine: {
        // 坐标轴线
        lineStyle: {
          // 属性lineStyle控制线条样式
          color: [
            [0.2, '#7FFF00'],
            [0.8, '#00FFFF'],
            [1, '#FF0000']
          ],
          //width: 6 外框的大小(环的宽度)
          width: 10
        }
      },
      axisTick: {
        // 坐标轴小标记
        //里面的线长是5(短线)
        length: 5, // 属性length控制线长
        lineStyle: {
          // 属性lineStyle控制线条样式
          color: '#76D9D7'
        }
      },
      splitLine: {
        // 分隔线
        length: 20, // 属性length控制线长
        lineStyle: {
          // 属性lineStyle(详见lineStyle)控制线条样式
          color: '#76D9D7'
        }
      },
      axisLabel: {
        color: '#76D9D7',
        distance: 15,
        fontSize: 15
      },
      pointer: {
        // 指针的大小
        width: 7,
        show: true
      },
      detail: {
        textStyle: {
          fontWeight: 'normal',
          // 里面文字下的数值大小(50)
          fontSize: 15,
          color: '#FFFFFF'
        },
        valueAnimation: true
      },
      progress: {
        show: true
      }
    }
  ]
})
/** 加载数据 */
const getSummary = () => {
  // 初始化命令图表
  usedDiskInstance()
}
const usedDiskInstance = async () => {
  try {
    const data = await StorageApi.getDiskInfo()
    disks.value = data
    disk.value = data[0]
    // data.forEach((disk) => {
      console.log(disk.value)
      console.log(disk.value.name)
      console.log(disk.value!.restPPT)
      // 仪表盘详情,用于显示数据。
      usedDiskEchartChika.series[0].detail = {
        show: true, // 是否显示详情,默认 true。
        offsetCenter: [0, '50%'], // 相对于仪表盘中心的偏移位置,数组第一项是水平方向的偏移,第二项是垂直方向的偏移。可以是绝对的数值,也可以是相对于仪表盘半径的百分比。
        color: 'auto', // 文字的颜色,默认 auto。
        fontSize: 30, // 文字的字体大小,默认 15。
        formatter: disk.value!.restPPT // 格式化函数或者字符串
      }
      console.log(disk.value.restPPT)
      usedDiskEchartChika.series[0].data[0] = {
        value: disk.value!.restPPT,
        name: '磁盘消耗'
      }
      console.log(disk.value)
      usedDiskEchartChika.tooltip = {
        formatter: '{b} <br/>{a} : ' + disk.value!.restPPT
      }
    // })
  } catch {}
}
/** 初始化 **/
onMounted(() => {
  readDiskInfo()
  // 读取 redis 信息
  // readDiskInfo()
  // // 加载数据
  getSummary()
})
</script>
src/views/system/appgroup/AppGroupForm.vue
对比新文件
@@ -0,0 +1,130 @@
<template>
  <Dialog v-model="dialogVisible" :title="dialogTitle" width="50%">
    <el-form
      ref="formRef"
      v-loading="formLoading"
      :model="formData"
      :rules="formRules"
      label-width="80px"
    >
      <el-row>
        <el-col :span="12">
          <el-form-item label="分组编号" prop="appCode">
            <el-input v-model="formData.code" placeholder="请输入应用分组编号" />
          </el-form-item>
        </el-col>
        <el-col :span="12">
          <el-form-item label="分组名称" prop="appName">
            <el-input v-model="formData.name" placeholder="请输入应用分组名称" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="24">
          <el-form-item label="分组图标" prop="icon">
            <UploadImg v-model="formData.icon" :limit="1" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="12">
          <el-form-item label="排序" prop="orderNum">
            <el-input-number v-model="formData.sort" placeholder="请输入排序" />
          </el-form-item>
        </el-col>
      </el-row>
      <el-row>
        <el-col :span="24">
          <el-form-item label="备注" prop="remark">
            <el-input v-model="formData.remark" clearable type="textarea" />
          </el-form-item>
        </el-col>
      </el-row>
    </el-form>
    <template #footer>
      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
      <el-button @click="dialogVisible = false">取 消</el-button>
    </template>
  </Dialog>
</template>
<script lang="ts" setup>
  import * as AppGroupApi from '@/api/system/appgroup'
  defineOptions({ name: 'SystemAppGroupForm' })
  const { t } = useI18n() // 国际化
  const message = useMessage() // 消息弹窗
  const dialogVisible = ref(false) // 弹窗的是否展示
  const dialogTitle = ref('') // 弹窗的标题
  const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
  const formType = ref('') // 表单的类型:create - 新增;update - 修改
  const formData = ref({
    id: undefined,
    code: undefined,
    name: undefined,
    icon: undefined,
    sort: undefined,
    remark: undefined
  })
  const formRules = reactive({
    code: [{ required: true, message: '分组编号不能为空', trigger: 'blur' }],
    name: [{ required: true, message: '分组名称不能为空', trigger: 'blur' }]
  })
  const formRef = ref() // 表单 Ref
  /** 打开弹窗 */
  const open = async (type: string, id?: number) => {
    dialogVisible.value = true
    dialogTitle.value = t('action.' + type)
    formType.value = type
    resetForm()
    // 修改时,设置数据
    if (id) {
      formLoading.value = true
      try {
        formData.value = await AppGroupApi.getAppGroup(id)
      } finally {
        formLoading.value = false
      }
    }
  }
  defineExpose({ open }) // 提供 open 方法,用于打开弹窗
  /** 提交表单 */
  const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
  const submitForm = async () => {
    // 校验表单
    if (!formRef) return
    const valid = await formRef.value.validate()
    if (!valid) return
    // 提交请求
    formLoading.value = true
    try {
      const data = formData.value as unknown as AppGroupApi.AppGroupVO
      if (formType.value === 'create') {
        await AppGroupApi.createAppGroup(data)
        message.success(t('common.createSuccess'))
      } else {
        await AppGroupApi.updateAppGroup(data)
        message.success(t('common.updateSuccess'))
      }
      dialogVisible.value = false
      // 发送操作成功的事件
      emit('success')
    } finally {
      formLoading.value = false
    }
  }
  /** 重置表单 */
  const resetForm = () => {
    formData.value = {
      id: undefined,
      code: undefined,
      name: undefined,
      icon: undefined,
      sort: undefined,
      remark: undefined,
    }
    formRef.value?.resetFields()
  }
</script>
src/views/system/appgroup/index.vue
对比新文件
@@ -0,0 +1,176 @@
<template>
  <!-- 搜索 -->
  <ContentWrap>
    <el-form
      class="-mb-15px"
      :model="queryParams"
      ref="queryFormRef"
      :inline="true"
      label-width="68px"
    >
      <el-form-item label="分组编号" prop="appCode">
        <el-input
          v-model="queryParams.code"
          placeholder="请输入分组编号"
          clearable
          @keyup.enter="handleQuery"
          class="!w-240px"
        />
      </el-form-item>
      <el-form-item label="分组名称" prop="appName">
        <el-input
          v-model="queryParams.name"
          placeholder="请输入分组名称"
          clearable
          @keyup.enter="handleQuery"
          class="!w-240px"
        />
      </el-form-item>
      <el-form-item>
        <el-button @click="handleQuery">
          <Icon icon="ep:search" class="mr-5px" />
          搜索
        </el-button>
        <el-button @click="resetQuery">
          <Icon icon="ep:refresh" class="mr-5px" />
          重置
        </el-button>
        <el-button
          type="primary"
          plain
          @click="openForm('create')"
          v-hasPermi="['system:app-group:create']"
        >
          <Icon icon="ep:plus" class="mr-5px" />
          新增
        </el-button>
      </el-form-item>
    </el-form>
  </ContentWrap>
  <!-- 列表 -->
  <ContentWrap>
    <el-table v-loading="loading" :data="list">
      <el-table-column label="分组编号" align="center" prop="code" />
      <el-table-column label="分组名称" align="center" prop="name" />
      <el-table-column label="分组图标" align="center" prop="logo">
        <template #default="scope">
          <img width="40px" height="40px" :src="scope.row.icon" />
        </template>
      </el-table-column>
      <el-table-column label="排序" align="center" prop="sort" />
      <el-table-column label="备注" align="center" prop="remark" width="200" />
      <el-table-column
        label="创建时间"
        align="center"
        prop="createTime"
        width="180"
        :formatter="dateFormatter"
      />
      <el-table-column label="操作" align="center" min-width="110" fixed="right">
        <template #default="scope">
          <el-button
            link
            type="primary"
            @click="openForm('update', scope.row.id)"
            v-hasPermi="['system:app-group:update']"
          >
            编辑
          </el-button>
          <el-button
            link
            type="danger"
            @click="handleDelete(scope.row.id)"
            v-hasPermi="['system:app-group:delete']"
          >
            删除
          </el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- 分页 -->
    <Pagination
      :total="total"
      v-model:page="queryParams.pageNo"
      v-model:limit="queryParams.pageSize"
      @pagination="getList"
    />
  </ContentWrap>
  <!-- 表单弹窗:添加/修改 -->
  <AppGroupForm ref="formRef" @success="getList" />
</template>
<script lang="ts" setup>
  import {dateFormatter} from '@/utils/formatTime'
  import * as AppGroupApi from '@/api/system/appgroup'
  import AppGroupForm from './AppGroupForm.vue'
  import * as TenantApi from "@/api/system/tenant";
  defineOptions({name: 'SystemAppGroup'})
  const message = useMessage() // 消息弹窗
  const {t} = useI18n() // 国际化
  const loading = ref(true) // 列表的加载中
  const total = ref(0) // 列表的总页数
  const list = ref([]) // 列表的数据
  const queryParams = reactive({
    pageNo: 1,
    pageSize: 10,
    code: undefined,
    name: undefined,
    createTime: []
  })
  const queryFormRef = ref() // 搜索的表单
  /** 查询列表 */
  const getList = async () => {
    loading.value = true
    try {
      const data = await AppGroupApi.getAppGroupPage(queryParams)
      list.value = data.list
      total.value = data.total
    } finally {
      loading.value = false
    }
  }
  /** 搜索按钮操作 */
  const handleQuery = () => {
    queryParams.pageNo = 1
    getList()
  }
  /** 重置按钮操作 */
  const resetQuery = () => {
    queryFormRef.value.resetFields()
    handleQuery()
  }
  /** 添加/修改操作 */
  const formRef = ref()
  const openForm = (type: string, id?: number) => {
    formRef.value.open(type, id)
  }
  /** 删除按钮操作 */
  const handleDelete = async (id: number) => {
    try {
      // 删除的二次确认
      await message.delConfirm()
      // 发起删除
      await AppGroupApi.deleteAppGroup(id)
      message.success(t('common.delSuccess'))
      // 刷新列表
      await getList()
    } catch {
    }
  }
  /** 初始化 **/
  onMounted(async () => {
    await getList()
  })
</script>
src/views/system/appmenu/AppMenuForm.vue
对比新文件
@@ -0,0 +1,256 @@
<template>
  <Dialog v-model="dialogVisible" :title="dialogTitle">
    <el-form
      ref="formRef"
      v-loading="formLoading"
      :model="formData"
      :rules="formRules"
      label-width="100px"
    >
      <el-form-item label="应用名称">
        <el-tree-select
          v-model="formData.parentId"
          :data="appMenuTree"
          :default-expanded-keys="[0]"
          :props="defaultProps"
          check-strictly
          node-key="id"
        />
      </el-form-item>
      <el-form-item label="菜单名称" prop="name">
        <el-input v-model="formData.name" clearable placeholder="请输入菜单名称" />
      </el-form-item>
      <el-form-item label="菜单类型" prop="type">
        <el-radio-group v-model="formData.type">
          <el-radio-button
            v-for="dict in getIntDictOptions(DICT_TYPE.SYSTEM_APP_MENU_TYPE)"
            :key="dict.label"
            :label="dict.value"
          >
            {{ dict.label }}
          </el-radio-button>
        </el-radio-group>
      </el-form-item>
      <el-form-item v-if="formData.type !== 3" label="菜单图标">
        <IconSelect v-model="formData.icon" clearable />
      </el-form-item>
      <el-form-item v-if="formData.type !== 3" label="路由地址" prop="path">
        <template #label>
          <Tooltip
            message="访问的路由地址,如:`user`。如需外网地址时,则以 `http(s)://` 开头"
            title="路由地址"
          />
        </template>
        <el-input v-model="formData.path" clearable placeholder="请输入路由地址" />
      </el-form-item>
      <el-form-item v-if="formData.type === 2" label="组件地址" prop="component">
        <el-input v-model="formData.component" clearable placeholder="例如说:system/user/index" />
      </el-form-item>
      <el-form-item v-if="formData.type === 2" label="组件名字" prop="componentName">
        <el-input v-model="formData.componentName" clearable placeholder="例如说:SystemUser" />
      </el-form-item>
      <el-form-item v-if="formData.type !== 1" label="权限标识" prop="permission">
        <template #label>
          <Tooltip
            message="Controller 方法上的权限字符,如:@PreAuthorize(`@ss.hasPermission('system:user:list')`)"
            title="权限标识"
          />
        </template>
        <el-input v-model="formData.permission" clearable placeholder="请输入权限标识" />
      </el-form-item>
      <el-form-item label="显示排序" prop="sort">
        <el-input-number v-model="formData.sort" :min="0" clearable controls-position="right" />
      </el-form-item>
      <el-form-item label="菜单状态" prop="status">
        <el-radio-group v-model="formData.status">
          <el-radio
            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
            :key="dict.label"
            :label="dict.value"
          >
            {{ dict.label }}
          </el-radio>
        </el-radio-group>
      </el-form-item>
      <el-form-item v-if="formData.type !== 3" label="显示状态" prop="visible">
        <template #label>
          <Tooltip message="选择隐藏时,路由将不会出现在侧边栏,但仍然可以访问" title="显示状态" />
        </template>
        <el-radio-group v-model="formData.visible">
          <el-radio key="true" :label="true" border>显示</el-radio>
          <el-radio key="false" :label="false" border>隐藏</el-radio>
        </el-radio-group>
      </el-form-item>
      <el-form-item v-if="formData.type !== 3" label="总是显示" prop="alwaysShow">
        <template #label>
          <Tooltip
            message="选择不是时,当该菜单只有一个子菜单时,不展示自己,直接展示子菜单"
            title="总是显示"
          />
        </template>
        <el-radio-group v-model="formData.alwaysShow">
          <el-radio key="true" :label="true" border>总是</el-radio>
          <el-radio key="false" :label="false" border>不是</el-radio>
        </el-radio-group>
      </el-form-item>
      <el-form-item v-if="formData.type === 2" label="缓存状态" prop="keepAlive">
        <template #label>
          <Tooltip
            message="选择缓存时,则会被 `keep-alive` 缓存,必须填写「组件名称」字段"
            title="缓存状态"
          />
        </template>
        <el-radio-group v-model="formData.keepAlive">
          <el-radio key="true" :label="true" border>缓存</el-radio>
          <el-radio key="false" :label="false" border>不缓存</el-radio>
        </el-radio-group>
      </el-form-item>
    </el-form>
    <template #footer>
      <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
      <el-button @click="dialogVisible = false">取 消</el-button>
    </template>
  </Dialog>
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as MenuApi from '@/api/system/menu'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
import { CommonStatusEnum, SystemAppMenuTypeEnum } from '@/utils/constants'
import { defaultProps, handleTree } from '@/utils/tree'
defineOptions({ name: 'SystemAppMenuForm' })
const { wsCache } = useCache()
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改
const formData = ref({
  id: undefined,
  name: '',
  permission: '',
  type: SystemAppMenuTypeEnum.DIR,
  sort: Number(undefined),
  parentId: 0,
  path: '',
  icon: '',
  component: '',
  componentName: '',
  status: CommonStatusEnum.ENABLE,
  visible: true,
  keepAlive: true,
  alwaysShow: true
})
const formRules = reactive({
  name: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }],
  sort: [{ required: true, message: '菜单顺序不能为空', trigger: 'blur' }],
  path: [{ required: true, message: '路由地址不能为空', trigger: 'blur' }],
  status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
/** 打开弹窗 */
const open = async (type: string, id?: number, parentId?: number) => {
  dialogVisible.value = true
  dialogTitle.value = t('action.' + type)
  formType.value = type
  resetForm()
  if (parentId) {
    formData.value.parentId = parentId
  }
  // 修改时,设置数据
  if (id) {
    formLoading.value = true
    try {
      formData.value = await MenuApi.getAppMenu(id)
    } finally {
      formLoading.value = false
    }
  }
  // 获得菜单列表
  await getTree()
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
  // 校验表单
  if (!formRef) return
  const valid = await formRef.value.validate()
  if (!valid) return
  // 提交请求
  formLoading.value = true
  try {
    if (
      formData.value.type === SystemAppMenuTypeEnum.DIR ||
      formData.value.type === SystemAppMenuTypeEnum.MENU
    ) {
      if (!isExternal(formData.value.path)) {
        if (formData.value.parentId === 0 && formData.value.path.charAt(0) !== '/') {
          message.error('路径必须以 / 开头')
          return
        } /*else if (formData.value.parentId !== 0 && formData.value.path.charAt(0) === '/') {
          message.error('路径不能以 / 开头')
          return
        }*/
      }
    }
    const data = formData.value as unknown as MenuApi.MenuVO
    if (formType.value === 'create') {
      await MenuApi.createAppMenu(data)
      message.success(t('common.createSuccess'))
    } else {
      await MenuApi.updateAppMenu(data)
      message.success(t('common.updateSuccess'))
    }
    dialogVisible.value = false
    // 发送操作成功的事件
    emit('success')
  } finally {
    formLoading.value = false
    // 清空,从而触发刷新
    wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
  }
}
/** 获取下拉框[上级菜单]的数据  */
const appMenuTree = ref<Tree[]>([]) // 树形结构
const getTree = async () => {
  appMenuTree.value = []
  const res = await MenuApi.getSimpleAppMenusList()
  let menu: Tree = { id: 0, name: '主应用', children: [] }
  menu.children = handleTree(res)
  appMenuTree.value.push(menu)
}
/** 重置表单 */
const resetForm = () => {
  formData.value = {
    id: undefined,
    name: '',
    permission: '',
    type: SystemAppMenuTypeEnum.DIR,
    sort: Number(undefined),
    parentId: 0,
    path: '',
    icon: '',
    component: '',
    componentName: '',
    status: CommonStatusEnum.ENABLE,
    visible: true,
    keepAlive: true,
    alwaysShow: true
  }
  formRef.value?.resetFields()
}
/** 判断 path 是不是外部的 HTTP 等链接 */
const isExternal = (path: string) => {
  return /^(https?:|mailto:|tel:)/.test(path)
}
</script>
src/views/system/appmenu/index.vue
对比新文件
@@ -0,0 +1,203 @@
<template>
  <!-- 搜索工作栏 -->
  <ContentWrap>
    <el-form
      ref="queryFormRef"
      :inline="true"
      :model="queryParams"
      class="-mb-15px"
      label-width="68px"
    >
      <el-form-item label="菜单名称" prop="name">
        <el-input
          v-model="queryParams.name"
          class="!w-240px"
          clearable
          placeholder="请输入菜单名称"
          @keyup.enter="handleQuery"
        />
      </el-form-item>
      <el-form-item label="状态" prop="status">
        <el-select
          v-model="queryParams.status"
          class="!w-240px"
          clearable
          placeholder="请选择菜单状态"
        >
          <el-option
            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
            :key="dict.value"
            :label="dict.label"
            :value="dict.value"
          />
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button @click="handleQuery">
          <Icon class="mr-5px" icon="ep:search" />
          搜索
        </el-button>
        <el-button @click="resetQuery">
          <Icon class="mr-5px" icon="ep:refresh" />
          重置
        </el-button>
        <el-button plain type="danger" @click="toggleExpandAll">
          <Icon class="mr-5px" icon="ep:sort" />
          展开/折叠
        </el-button>
        <el-button plain @click="refreshMenu">
          <Icon class="mr-5px" icon="ep:refresh" />
          刷新菜单缓存
        </el-button>
      </el-form-item>
    </el-form>
  </ContentWrap>
  <!-- 列表 -->
  <ContentWrap>
    <el-table
      v-if="refreshTable"
      v-loading="loading"
      :data="list"
      :default-expand-all="isExpandAll"
      row-key="id"
    >
      <el-table-column :show-overflow-tooltip="true" label="菜单名称" prop="name" width="250" />
      <el-table-column align="center" label="图标" prop="icon" width="100">
        <template #default="scope">
          <Icon :icon="scope.row.icon" />
        </template>
      </el-table-column>
      <el-table-column label="排序" prop="sort" width="60" />
      <el-table-column :show-overflow-tooltip="true" label="权限标识" prop="permission" />
      <el-table-column :show-overflow-tooltip="true" label="组件路径" prop="component" />
      <el-table-column :show-overflow-tooltip="true" label="组件名称" prop="componentName" />
      <el-table-column label="状态" prop="status" width="80">
        <template #default="scope">
          <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
        </template>
      </el-table-column>
      <el-table-column align="center" label="操作">
        <template #default="scope">
          <el-button
            v-hasPermi="['system:app-menu:update']"
            link
            type="primary"
            @click="openForm('update', scope.row.id)"
          >
            修改
          </el-button>
          <el-button
            v-hasPermi="['system:app-menu:create']"
            link
            type="primary"
            @click="openForm('create', undefined, scope.row.id)"
          >
            新增
          </el-button>
          <el-button
            v-hasPermi="['system:app-menu:delete']"
            link
            type="danger"
            @click="handleDelete(scope.row.id)"
          >
            删除
          </el-button>
        </template>
      </el-table-column>
    </el-table>
  </ContentWrap>
  <!-- 表单弹窗:添加/修改 -->
  <AppMenuForm ref="formRef" @success="getList" />
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { handleTree } from '@/utils/tree'
import * as MenuApi from '@/api/system/menu'
import AppMenuForm from './AppMenuForm.vue'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
defineOptions({ name: 'SystemAppMenu' })
const { wsCache } = useCache()
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const loading = ref(true) // 列表的加载中
const list = ref<any>([]) // 列表的数据
const queryParams = reactive({
  name: undefined,
  status: undefined
})
const queryFormRef = ref() // 搜索的表单
const isExpandAll = ref(false) // 是否展开,默认全部折叠
const refreshTable = ref(true) // 重新渲染表格状态
/** 查询列表 */
const getList = async () => {
  loading.value = true
  try {
    const data = await MenuApi.getAppMenuList(queryParams)
    list.value = handleTree(data)
  } finally {
    loading.value = false
  }
}
/** 搜索按钮操作 */
const handleQuery = () => {
  getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
  queryFormRef.value.resetFields()
  handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number, parentId?: number) => {
  formRef.value.open(type, id, parentId)
}
/** 展开/折叠操作 */
const toggleExpandAll = () => {
  refreshTable.value = false
  isExpandAll.value = !isExpandAll.value
  nextTick(() => {
    refreshTable.value = true
  })
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
  try {
    // 删除的二次确认
    await message.delConfirm()
    // 发起删除
    await MenuApi.deleteAppMenu(id)
    message.success(t('common.delSuccess'))
    // 刷新列表
    await getList()
  } catch {}
}
/** 刷新菜单缓存按钮操作 */
const refreshMenu = async () => {
  try {
    await message.confirm('即将更新缓存刷新浏览器!', '刷新菜单缓存')
    // 清空,从而触发刷新
    wsCache.delete(CACHE_KEY.USER)
    wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
    // 刷新浏览器
    location.reload()
  } catch {}
}
/** 初始化 **/
onMounted(() => {
  getList()
})
</script>