兰宝车间质量管理系统-前端
疯狂的狮子Li
2023-04-03 97187b246b94dd58cb585ebaed7e8644d2f00119
update 调整代码格式
已修改24个文件
2319 ■■■■ 文件已修改
src/components/Hamburger/index.vue 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/HeaderSearch/index.vue 246 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/IconSelect/index.vue 90 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/ImagePreview/index.vue 90 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/SizeSelect/index.vue 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/SvgIcon/index.vue 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/TopNav/index.vue 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/TreeSelect/index.vue 116 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/iFrame/index.vue 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/IframeToggle/index.vue 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Settings/index.vue 204 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/Link.vue 70 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/Logo.vue 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/Sidebar/index.vue 56 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/components/TagsView/ScrollPane.vue 104 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/index.vue 106 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/monitor/cache/index.vue 129 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/config/index.vue 280 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/index.vue 56 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/resetPwd.vue 90 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/userAvatar.vue 202 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/system/user/profile/userInfo.vue 80 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/basicInfoForm.vue 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tool/gen/genInfoForm.vue 146 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Hamburger/index.vue
@@ -1,17 +1,3 @@
<script setup lang="ts">
defineProps({
  isActive: {
    type: Boolean,
    default: false
  }
})
const emit = defineEmits(['toggleClick'])
const toggleClick = () => {
  emit('toggleClick');
}
</script>
<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">
@@ -22,6 +8,20 @@
  </div>
</template>
<script setup lang="ts">
defineProps({
    isActive: {
        type: Boolean,
        default: false
    }
})
const emit = defineEmits(['toggleClick'])
const toggleClick = () => {
    emit('toggleClick');
}
</script>
<style scoped>
.hamburger {
  display: inline-block;
src/components/HeaderSearch/index.vue
@@ -1,126 +1,3 @@
<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'
type Router = Array<{
  path: string;
  title: string[];
}>
const search = ref('');
const options = ref<any>([]);
const searchPool = ref<Router>([]);
const show = ref(false);
const fuse = ref();
const headerSearchSelectRef = ref(ElSelect);
const router = useRouter();
const routes = computed(() => usePermissionStore().routes);
const click = () => {
  show.value = !show.value
  if (show.value) {
    headerSearchSelectRef.value && headerSearchSelectRef.value.focus()
  }
};
const close = () => {
  headerSearchSelectRef.value && headerSearchSelectRef.value.blur()
  options.value = []
  show.value = false
}
const change = (val: any) => {
  const path = val.path;
  if (isHttp(path)) {
    // http(s):// 路径新窗口打开
    const pindex = path.indexOf("http");
    window.open(path.substr(pindex, path.length), "_blank");
  } else {
    router.push(path)
  }
  search.value = ''
  options.value = []
  nextTick(() => {
    show.value = false
  })
}
const initFuse = (list: Router) => {
  fuse.value = new Fuse(list, {
    shouldSort: true,
    threshold: 0.4,
    location: 0,
    distance: 100,
    minMatchCharLength: 1,
    keys: [{
      name: 'title',
      weight: 0.7
    }, {
      name: 'path',
      weight: 0.3
    }]
  })
}
// Filter out the routes that can be displayed in the sidebar
// And generate the internationalized title
const generateRoutes = (routes: RouteOption[], basePath = '', prefixTitle: string[] = []) => {
  let res: Router = []
  routes.forEach(r => {
    // skip hidden router
    if (!r.hidden) {
      const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
      const data = {
        path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
        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];
        }
      }
    }
  })
  return res;
}
const querySearch = (query: string) => {
  if (query !== '') {
    options.value = fuse.value.search(query)
  } else {
    options.value = []
  }
}
onMounted(() => {
  searchPool.value = generateRoutes(routes.value);
})
watchEffect(() => {
  searchPool.value = generateRoutes(routes.value)
})
watch(show, (value) => {
  if (value) {
    document.body.addEventListener('click', close)
  } else {
    document.body.removeEventListener('click', close)
  }
})
watch(searchPool, (list) => {
  initFuse(list)
})
</script>
<template>
  <div :class="{ 'show': show }" class="header-search">
    <svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
@@ -140,6 +17,129 @@
  </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'
type Router = Array<{
    path: string;
    title: string[];
}>
const search = ref('');
const options = ref<any>([]);
const searchPool = ref<Router>([]);
const show = ref(false);
const fuse = ref();
const headerSearchSelectRef = ref(ElSelect);
const router = useRouter();
const routes = computed(() => usePermissionStore().routes);
const click = () => {
    show.value = !show.value
    if (show.value) {
        headerSearchSelectRef.value && headerSearchSelectRef.value.focus()
    }
};
const close = () => {
    headerSearchSelectRef.value && headerSearchSelectRef.value.blur()
    options.value = []
    show.value = false
}
const change = (val: any) => {
    const path = val.path;
    if (isHttp(path)) {
        // http(s):// 路径新窗口打开
        const pindex = path.indexOf("http");
        window.open(path.substr(pindex, path.length), "_blank");
    } else {
        router.push(path)
    }
    search.value = ''
    options.value = []
    nextTick(() => {
        show.value = false
    })
}
const initFuse = (list: Router) => {
    fuse.value = new Fuse(list, {
        shouldSort: true,
        threshold: 0.4,
        location: 0,
        distance: 100,
        minMatchCharLength: 1,
        keys: [{
            name: 'title',
            weight: 0.7
        }, {
            name: 'path',
            weight: 0.3
        }]
    })
}
// Filter out the routes that can be displayed in the sidebar
// And generate the internationalized title
const generateRoutes = (routes: RouteOption[], basePath = '', prefixTitle: string[] = []) => {
    let res: Router = []
    routes.forEach(r => {
        // skip hidden router
        if (!r.hidden) {
            const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
            const data = {
                path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
                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];
                }
            }
        }
    })
    return res;
}
const querySearch = (query: string) => {
    if (query !== '') {
        options.value = fuse.value.search(query)
    } else {
        options.value = []
    }
}
onMounted(() => {
    searchPool.value = generateRoutes(routes.value);
})
watchEffect(() => {
    searchPool.value = generateRoutes(routes.value)
})
watch(show, (value) => {
    if (value) {
        document.body.addEventListener('click', close)
    } else {
        document.body.removeEventListener('click', close)
    }
})
watch(searchPool, (list) => {
    initFuse(list)
})
</script>
<style lang="scss" scoped>
.header-search {
  font-size: 0 !important;
src/components/IconSelect/index.vue
@@ -1,48 +1,3 @@
<script setup lang="ts">
import icons from '@/components/IconSelect/requireIcons';
const props = defineProps({
  modelValue: {
    type: String,
    require: true
  },
  width: {
    type: String,
    require: false,
    default: '400px'
  }
});
const emit = defineEmits(['update:modelValue']);
const visible = ref(false);
const { modelValue, width } = toRefs(props);
const iconNames = ref<string[]>(icons);
const filterValue = ref('');
/**
 * 筛选图标
 */
const filterIcons = () => {
  if (filterValue.value) {
    iconNames.value = icons.filter(iconName =>
      iconName.includes(filterValue.value)
    );
  } else {
    iconNames.value = icons;
  }
}
/**
 * 选择图标
 * @param iconName 选择的图标名称
 */
const selectedIcon = (iconName: string) => {
  emit('update:modelValue', iconName);
  visible.value = false;
}
</script>
<template>
  <div class="relative" :style="{ width: width }">
    <el-input v-model="modelValue" readonly @click="visible = !visible" placeholder="点击选择图标">
@@ -74,6 +29,51 @@
  </div>
</template>
<script setup lang="ts">
import icons from '@/components/IconSelect/requireIcons';
const props = defineProps({
    modelValue: {
        type: String,
        require: true
    },
    width: {
        type: String,
        require: false,
        default: '400px'
    }
});
const emit = defineEmits(['update:modelValue']);
const visible = ref(false);
const { modelValue, width } = toRefs(props);
const iconNames = ref<string[]>(icons);
const filterValue = ref('');
/**
 * 筛选图标
 */
const filterIcons = () => {
    if (filterValue.value) {
        iconNames.value = icons.filter(iconName =>
            iconName.includes(filterValue.value)
        );
    } else {
        iconNames.value = icons;
    }
}
/**
 * 选择图标
 * @param iconName 选择的图标名称
 */
const selectedIcon = (iconName: string) => {
    emit('update:modelValue', iconName);
    visible.value = false;
}
</script>
<style scoped lang="scss">
.el-divider--horizontal {
  margin: 10px auto !important;
src/components/ImagePreview/index.vue
@@ -1,48 +1,3 @@
<script setup lang="ts">
const props = defineProps({
  src: {
    type: String,
    default: ""
  },
  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;
});
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;
});
const realWidth = computed(() =>
  typeof props.width == "string" ? props.width : `${props.width}px`
);
const realHeight = computed(() =>
  typeof props.height == "string" ? props.height : `${props.height}px`
);
</script>
<template>
  <el-image :src="`${realSrc}`" fit="cover" :style="`width:${realWidth};height:${realHeight};`" :preview-src-list="realSrcList" preview-teleported>
    <template #error>
@@ -53,6 +8,51 @@
  </el-image>
</template>
<script setup lang="ts">
const props = defineProps({
    src: {
        type: String,
        default: ""
    },
    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;
});
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;
});
const realWidth = computed(() =>
    typeof props.width == "string" ? props.width : `${props.width}px`
);
const realHeight = computed(() =>
    typeof props.height == "string" ? props.height : `${props.height}px`
);
</script>
<style lang="scss" scoped>
.el-image {
  border-radius: 5px;
src/components/SizeSelect/index.vue
@@ -1,20 +1,3 @@
<script setup lang="ts">
import useAppStore from "@/store/modules/app";
const appStore = useAppStore();
const size = computed(() => appStore.size);
const sizeOptions = ref([
  { label: "较大", value: "large" },
  { label: "默认", value: "default" },
  { label: "稍小", value: "small" },
]);
const handleSetSize = (size: string) => {
  appStore.setSize(size);
}
</script>
<template>
  <div>
    <el-dropdown trigger="click" @command="handleSetSize">
@@ -32,6 +15,23 @@
  </div>
</template>
<script setup lang="ts">
import useAppStore from "@/store/modules/app";
const appStore = useAppStore();
const size = computed(() => appStore.size);
const sizeOptions = ref([
    { label: "较大", value: "large" },
    { label: "默认", value: "default" },
    { label: "稍小", value: "small" },
]);
const handleSetSize = (size: string) => {
    appStore.setSize(size);
}
</script>
<style lang="scss" scoped>
.size-icon--style {
  font-size: 18px;
src/components/SvgIcon/index.vue
@@ -1,33 +1,33 @@
<script setup lang="ts">
const props = defineProps({
  iconClass: {
    type: String,
    required: true
  },
  className: {
    type: String,
    default: ''
  },
  color: {
    type: String,
    default: ''
  },
})
const iconName =  computed(() => `#icon-${props.iconClass}`);
const svgClass = computed(() => {
  if (props.className) {
    return `svg-icon ${props.className}`
  }
  return 'svg-icon'
})
</script>
<template>
  <svg :class="svgClass" aria-hidden="true">
    <use :xlink:href="iconName" :fill="color" />
  </svg>
</template>
<script setup lang="ts">
const props = defineProps({
    iconClass: {
        type: String,
        required: true
    },
    className: {
        type: String,
        default: ''
    },
    color: {
        type: String,
        default: ''
    },
})
const iconName =  computed(() => `#icon-${props.iconClass}`);
const svgClass = computed(() => {
    if (props.className) {
        return `svg-icon ${props.className}`
    }
    return 'svg-icon'
})
</script>
<style scope lang="scss">
.sub-el-icon,
.nav-icon {
src/components/TopNav/index.vue
@@ -1,21 +1,21 @@
<template>
    <el-menu :default-active="activeMenu" mode="horizontal" @select="handleSelect" :ellipsis="false">
        <template v-for="(item, index) in topMenus">
            <el-menu-item :style="{'--theme': theme}" :index="item.path" :key="index" v-if="index < visibleNumber"
            ><svg-icon :icon-class="item.meta ? item.meta.icon : '' " /> {{ item.meta?.title }}</el-menu-item
            >
        </template>
  <el-menu :default-active="activeMenu" mode="horizontal" @select="handleSelect" :ellipsis="false">
    <template v-for="(item, index) in topMenus">
      <el-menu-item :style="{'--theme': theme}" :index="item.path" :key="index" v-if="index < visibleNumber"
        ><svg-icon :icon-class="item.meta ? item.meta.icon : '' " /> {{ item.meta?.title }}</el-menu-item
      >
    </template>
        <!-- 顶部菜单超出数量折叠 -->
        <el-sub-menu :style="{'--theme': theme}" index="more" v-if="topMenus.length > visibleNumber">
            <template #title>更多菜单</template>
            <template v-for="(item, index) in topMenus">
                <el-menu-item :index="item.path" :key="index" v-if="index >= visibleNumber"
                ><svg-icon :icon-class="item.meta ? item.meta.icon : '' " /> {{ item.meta?.title }}</el-menu-item
                >
            </template>
        </el-sub-menu>
    </el-menu>
    <!-- 顶部菜单超出数量折叠 -->
    <el-sub-menu :style="{'--theme': theme}" index="more" v-if="topMenus.length > visibleNumber">
      <template #title>更多菜单</template>
      <template v-for="(item, index) in topMenus">
        <el-menu-item :index="item.path" :key="index" v-if="index >= visibleNumber"
          ><svg-icon :icon-class="item.meta ? item.meta.icon : '' " /> {{ item.meta?.title }}</el-menu-item
        >
      </template>
    </el-sub-menu>
  </el-menu>
</template>
<script setup lang="ts">
src/components/TreeSelect/index.vue
@@ -1,39 +1,69 @@
<template>
  <div class="el-tree-select">
    <el-select
      style="width: 100%"
      v-model="valueId"
      ref="treeSelect"
      :filterable="true"
      :clearable="true"
      @clear="clearHandle"
      :filter-method="selectFilterData"
      :placeholder="placeholder"
    >
      <el-option :value="valueId" :label="valueTitle">
        <el-tree
          id="tree-option"
          ref="selectTree"
          :accordion="accordion"
          :data="options"
          :props="objMap"
          :node-key="objMap.value"
          :expand-on-click-node="false"
          :default-expanded-keys="defaultExpandedKey"
          :filter-node-method="filterNode"
          @node-click="handleNodeClick"
        ></el-tree>
      </el-option>
    </el-select>
  </div>
</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: ''
  }
})
@@ -45,7 +75,7 @@
const valueId = computed({
  get: () => props.value,
  set: (val) => {
    emit('update:value', val)
  emit('update:value', val)
  }
});
const valueTitle = ref('');
@@ -53,17 +83,17 @@
function 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] // 设置默认展开
      }
    } else {
      clearHandle()
  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()
  }
  })
}
function handleNodeClick(node: any) {
@@ -126,33 +156,3 @@
  color: $--color-primary;
}
</style>
<template>
  <div class="el-tree-select">
    <el-select
      style="width: 100%"
      v-model="valueId"
      ref="treeSelect"
      :filterable="true"
      :clearable="true"
      @clear="clearHandle"
      :filter-method="selectFilterData"
      :placeholder="placeholder"
    >
      <el-option :value="valueId" :label="valueTitle">
        <el-tree
          id="tree-option"
          ref="selectTree"
          :accordion="accordion"
          :data="options"
          :props="objMap"
          :node-key="objMap.value"
          :expand-on-click-node="false"
          :default-expanded-keys="defaultExpandedKey"
          :filter-node-method="filterNode"
          @node-click="handleNodeClick"
        ></el-tree>
      </el-option>
    </el-select>
  </div>
</template>
src/components/iFrame/index.vue
@@ -1,3 +1,9 @@
<template>
  <div v-loading="loading" :style="'height:' + height">
    <iframe :src="url" frameborder="no" style="width: 100%; height: 100%" scrolling="auto" />
  </div>
</template>
<script setup lang="ts">
const props = defineProps({
  src: {
@@ -19,9 +25,3 @@
  };
})
</script>
<template>
  <div v-loading="loading" :style="'height:' + height">
    <iframe :src="url" frameborder="no" style="width: 100%; height: 100%" scrolling="auto" />
  </div>
</template>
src/layout/components/IframeToggle/index.vue
@@ -1,11 +1,3 @@
<script setup lang="ts">
import InnerLink from "../InnerLink/index.vue";
import useTagsViewStore from '@/store/modules/tagsView';
const route = useRoute();
const tagsViewStore = useTagsViewStore()
</script>
<template>
  <transition-group name="fade-transform" mode="out-in">
    <inner-link
@@ -17,3 +9,11 @@
    ></inner-link>
  </transition-group>
</template>
<script setup lang="ts">
import InnerLink from "../InnerLink/index.vue";
import useTagsViewStore from '@/store/modules/tagsView';
const route = useRoute();
const tagsViewStore = useTagsViewStore()
</script>
src/layout/components/Settings/index.vue
@@ -1,105 +1,3 @@
<script setup lang="ts">
import { useDynamicTitle } from '@/utils/dynamicTitle'
import useAppStore from '@/store/modules/app'
import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission'
import { handleThemeStyle } from '@/utils/theme'
import { ComponentInternalInstance } from "vue";
import { SettingTypeEnum } from "@/enums/SettingTypeEnum";
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const appStore = useAppStore()
const settingsStore = useSettingsStore()
const permissionStore = usePermissionStore()
const showSettings = ref(false);
const theme = ref(settingsStore.theme);
const sideTheme = ref(settingsStore.sideTheme);
const storeSettings = computed(() => settingsStore);
const predefineColors = ref(["#409EFF", "#ff4500", "#ff8c00", "#ffd700", "#90ee90", "#00ced1", "#1e90ff", "#c71585"]);
/** 是否需要topnav */
const topNav = computed({
  get: () => storeSettings.value.topNav,
  set: (val) => {
    settingsStore.changeSetting({ key: SettingTypeEnum.TOP_NAV, value: val })
    if (!val) {
      appStore.toggleSideBarHide(false);
      permissionStore.setSidebarRouters(permissionStore.defaultRoutes);
    }
  }
})
/** 是否需要tagview */
const tagsView = computed({
  get: () => storeSettings.value.tagsView,
  set: (val) => {
    settingsStore.changeSetting({ key: SettingTypeEnum.TAGS_VIEW, value: val })
  }
})
/**是否需要固定头部 */
const fixedHeader = computed({
  get: () => storeSettings.value.fixedHeader,
  set: (val) => {
    settingsStore.changeSetting({ key: SettingTypeEnum.FIXED_HEADER, value: val })
  }
})
/**是否需要侧边栏的logo */
const sidebarLogo = computed({
  get: () => storeSettings.value.sidebarLogo,
  set: (val) => {
    settingsStore.changeSetting({ key: SettingTypeEnum.SIDEBAR_LOGO, value: val })
  }
})
/**是否需要侧边栏的动态网页的title */
const dynamicTitle = computed({
  get: () => storeSettings.value.dynamicTitle,
  set: (val) => {
    settingsStore.changeSetting({ key: SettingTypeEnum.DYNAMIC_TITLE, value: val })
    // 动态设置网页标题
    useDynamicTitle()
  }
})
const themeChange = (val: string | null) => {
  settingsStore.changeSetting({ key: SettingTypeEnum.THEME, value: val })
  theme.value = val;
  if (val) {
    handleThemeStyle(val);
  }
}
const handleTheme = (val: string) => {
  settingsStore.changeSetting({ key: SettingTypeEnum.SIDE_THEME, value: val })
  sideTheme.value = val;
}
const saveSetting = () => {
  proxy?.$modal.loading("正在保存到本地,请稍候...");
  let layoutSetting = {
    "topNav": storeSettings.value.topNav,
    "tagsView": storeSettings.value.tagsView,
    "fixedHeader": storeSettings.value.fixedHeader,
    "sidebarLogo": storeSettings.value.sidebarLogo,
    "dynamicTitle": storeSettings.value.dynamicTitle,
    "sideTheme": storeSettings.value.sideTheme,
    "theme": storeSettings.value.theme
  };
  localStorage.setItem("layout-setting", JSON.stringify(layoutSetting));
  setTimeout(() => {proxy?.$modal.closeLoading()}, 1000)
}
const resetSetting = () => {
  proxy?.$modal.loading("正在清除设置缓存并刷新,请稍候...");
  localStorage.removeItem("layout-setting")
  setTimeout("window.location.reload()", 1000)
}
const openSetting = () => {
  showSettings.value = true;
}
defineExpose({
  openSetting,
})
</script>
<template>
  <el-drawer v-model="showSettings" :withHeader="false" direction="rtl" size="300px" close-on-click-modal>
    <div class="setting-drawer-title">
@@ -183,6 +81,108 @@
  </el-drawer>
</template>
<script setup lang="ts">
import { useDynamicTitle } from '@/utils/dynamicTitle'
import useAppStore from '@/store/modules/app'
import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission'
import { handleThemeStyle } from '@/utils/theme'
import { ComponentInternalInstance } from "vue";
import { SettingTypeEnum } from "@/enums/SettingTypeEnum";
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const appStore = useAppStore()
const settingsStore = useSettingsStore()
const permissionStore = usePermissionStore()
const showSettings = ref(false);
const theme = ref(settingsStore.theme);
const sideTheme = ref(settingsStore.sideTheme);
const storeSettings = computed(() => settingsStore);
const predefineColors = ref(["#409EFF", "#ff4500", "#ff8c00", "#ffd700", "#90ee90", "#00ced1", "#1e90ff", "#c71585"]);
/** 是否需要topnav */
const topNav = computed({
    get: () => storeSettings.value.topNav,
    set: (val) => {
        settingsStore.changeSetting({ key: SettingTypeEnum.TOP_NAV, value: val })
        if (!val) {
            appStore.toggleSideBarHide(false);
            permissionStore.setSidebarRouters(permissionStore.defaultRoutes);
        }
    }
})
/** 是否需要tagview */
const tagsView = computed({
    get: () => storeSettings.value.tagsView,
    set: (val) => {
        settingsStore.changeSetting({ key: SettingTypeEnum.TAGS_VIEW, value: val })
    }
})
/**是否需要固定头部 */
const fixedHeader = computed({
    get: () => storeSettings.value.fixedHeader,
    set: (val) => {
        settingsStore.changeSetting({ key: SettingTypeEnum.FIXED_HEADER, value: val })
    }
})
/**是否需要侧边栏的logo */
const sidebarLogo = computed({
    get: () => storeSettings.value.sidebarLogo,
    set: (val) => {
        settingsStore.changeSetting({ key: SettingTypeEnum.SIDEBAR_LOGO, value: val })
    }
})
/**是否需要侧边栏的动态网页的title */
const dynamicTitle = computed({
    get: () => storeSettings.value.dynamicTitle,
    set: (val) => {
        settingsStore.changeSetting({ key: SettingTypeEnum.DYNAMIC_TITLE, value: val })
        // 动态设置网页标题
        useDynamicTitle()
    }
})
const themeChange = (val: string | null) => {
    settingsStore.changeSetting({ key: SettingTypeEnum.THEME, value: val })
    theme.value = val;
    if (val) {
        handleThemeStyle(val);
    }
}
const handleTheme = (val: string) => {
    settingsStore.changeSetting({ key: SettingTypeEnum.SIDE_THEME, value: val })
    sideTheme.value = val;
}
const saveSetting = () => {
    proxy?.$modal.loading("正在保存到本地,请稍候...");
    let layoutSetting = {
        "topNav": storeSettings.value.topNav,
        "tagsView": storeSettings.value.tagsView,
        "fixedHeader": storeSettings.value.fixedHeader,
        "sidebarLogo": storeSettings.value.sidebarLogo,
        "dynamicTitle": storeSettings.value.dynamicTitle,
        "sideTheme": storeSettings.value.sideTheme,
        "theme": storeSettings.value.theme
    };
    localStorage.setItem("layout-setting", JSON.stringify(layoutSetting));
    setTimeout(() => {proxy?.$modal.closeLoading()}, 1000)
}
const resetSetting = () => {
    proxy?.$modal.loading("正在清除设置缓存并刷新,请稍候...");
    localStorage.removeItem("layout-setting")
    setTimeout("window.location.reload()", 1000)
}
const openSetting = () => {
    showSettings.value = true;
}
defineExpose({
    openSetting,
})
</script>
<style lang="scss" scoped>
.setting-drawer-title {
  margin-bottom: 12px;
src/layout/components/Sidebar/Link.vue
@@ -1,40 +1,40 @@
<script setup lang="ts">
import { isExternal } from '@/utils/validate'
const props = defineProps({
  to: {
    type: [String, Object],
    required: true
  }
})
const isExt = computed(() => {
  return isExternal(props.to as string)
})
const type = computed(() => {
  if (isExt.value) {
    return 'a'
  }
  return 'router-link'
})
function linkProps() {
  if (isExt.value) {
    return {
      href: props.to,
      target: '_blank',
      rel: 'noopener'
    }
  }
  return {
    to: props.to
  }
}
</script>
<template>
  <component :is="type" v-bind="linkProps()">
    <slot />
  </component>
</template>
<script setup lang="ts">
import { isExternal } from '@/utils/validate'
const props = defineProps({
    to: {
        type: [String, Object],
        required: true
    }
})
const isExt = computed(() => {
    return isExternal(props.to as string)
})
const type = computed(() => {
    if (isExt.value) {
        return 'a'
    }
    return 'router-link'
})
function linkProps() {
    if (isExt.value) {
        return {
            href: props.to,
            target: '_blank',
            rel: 'noopener'
        }
    }
    return {
        to: props.to
    }
}
</script>
src/layout/components/Sidebar/Logo.vue
@@ -1,22 +1,3 @@
<script setup lang="ts">
import variables from '@/assets/styles/variables.module.scss'
import logo from '@/assets/logo/logo.png'
import useSettingsStore from '@/store/modules/settings'
import { ComponentInternalInstance } from "vue";
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
defineProps({
  collapse: {
    type: Boolean,
    required: true
  }
})
const title = ref('RuoYi-Vue-Plus');
const settingsStore = useSettingsStore();
const sideTheme = computed(() => settingsStore.sideTheme);
</script>
<template>
  <div
    class="sidebar-logo-container"
@@ -40,6 +21,25 @@
  </div>
</template>
<script setup lang="ts">
import variables from '@/assets/styles/variables.module.scss'
import logo from '@/assets/logo/logo.png'
import useSettingsStore from '@/store/modules/settings'
import { ComponentInternalInstance } from "vue";
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
defineProps({
    collapse: {
        type: Boolean,
        required: true
    }
})
const title = ref('RuoYi-Vue-Plus');
const settingsStore = useSettingsStore();
const sideTheme = computed(() => settingsStore.sideTheme);
</script>
<style lang="scss" scoped>
.sidebarLogoFade-enter-active {
  transition: opacity 1.5s;
src/layout/components/Sidebar/index.vue
@@ -1,3 +1,25 @@
<template>
  <div :class="{ 'has-logo': showLogo }" :style="{ backgroundColor: bgColor }">
    <logo v-if="showLogo" :collapse="isCollapse" />
    <el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper">
      <transition :enter-active-class="proxy?.animate.menuSearchAnimate.enter" mode="out-in">
        <el-menu
          :default-active="activeMenu as string"
          :collapse="isCollapse"
          :background-color="bgColor"
          :text-color="textColor"
          :unique-opened="true"
          :active-text-color="theme"
          :collapse-transition="false"
          mode="vertical"
        >
          <sidebar-item v-for="(route, index) in sidebarRouters" :key="route.path + index" :item="route" :base-path="route.path" />
        </el-menu>
      </transition>
    </el-scrollbar>
  </div>
</template>
<script setup lang="ts">
import Logo from './Logo.vue'
import SidebarItem from './SidebarItem.vue'
@@ -20,36 +42,14 @@
const isCollapse = computed(() => !appStore.sidebar.opened);
const activeMenu = computed(() => {
  const { meta, path } = route;
  // if set path, the sidebar will highlight the path you set
  if (meta.activeMenu) {
    return meta.activeMenu;
  }
  return path;
    const { meta, path } = route;
    // if set path, the sidebar will highlight the path you set
    if (meta.activeMenu) {
        return meta.activeMenu;
    }
    return path;
})
const bgColor = computed(() => sideTheme.value === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground);
const textColor = computed(() => sideTheme.value === 'theme-dark' ? variables.menuColor : variables.menuLightColor);
</script>
<template>
  <div :class="{ 'has-logo': showLogo }" :style="{ backgroundColor: bgColor }">
    <logo v-if="showLogo" :collapse="isCollapse" />
    <el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper">
      <transition :enter-active-class="proxy?.animate.menuSearchAnimate.enter" mode="out-in">
        <el-menu
          :default-active="activeMenu as string"
          :collapse="isCollapse"
          :background-color="bgColor"
          :text-color="textColor"
          :unique-opened="true"
          :active-text-color="theme"
          :collapse-transition="false"
          mode="vertical"
        >
          <sidebar-item v-for="(route, index) in sidebarRouters" :key="route.path + index" :item="route" :base-path="route.path" />
        </el-menu>
      </transition>
    </el-scrollbar>
  </div>
</template>
src/layout/components/TagsView/ScrollPane.vue
@@ -1,3 +1,9 @@
<template>
  <el-scrollbar ref="scrollContainerRef" :vertical="false" class="scroll-container" @wheel.prevent="handleScroll">
    <slot />
  </el-scrollbar>
</template>
<script setup lang="ts">
import useTagsViewStore from '@/store/modules/tagsView'
import { ElScrollbar } from 'element-plus';
@@ -8,83 +14,77 @@
const scrollWrapper = computed(() => scrollContainerRef.value.$refs.wrapRef);
onMounted(() => {
  scrollWrapper.value.addEventListener('scroll', emitScroll, true)
    scrollWrapper.value.addEventListener('scroll', emitScroll, true)
})
onBeforeUnmount(() => {
  scrollWrapper.value.removeEventListener('scroll', emitScroll)
    scrollWrapper.value.removeEventListener('scroll', emitScroll)
})
const handleScroll = (e: WheelEvent) => {
  const eventDelta = (e as any).wheelDelta || -e.deltaY * 40
  const $scrollWrapper = scrollWrapper.value;
  $scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
    const eventDelta = (e as any).wheelDelta || -e.deltaY * 40
    const $scrollWrapper = scrollWrapper.value;
    $scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
}
const emits = defineEmits(['scroll'])
const emitScroll = () => {
  emits('scroll')
    emits('scroll')
}
const tagsViewStore = useTagsViewStore()
const visitedViews = computed(() => tagsViewStore.visitedViews);
const moveToTarget = (currentTag: TagView) => {
  const $container = scrollContainerRef.value.$el
  const $containerWidth = $container.offsetWidth
  const $scrollWrapper = scrollWrapper.value;
    const $container = scrollContainerRef.value.$el
    const $containerWidth = $container.offsetWidth
    const $scrollWrapper = scrollWrapper.value;
  let firstTag = null
  let lastTag = null
    let firstTag = null
    let lastTag = null
  // find first tag and last tag
  if (visitedViews.value.length > 0) {
    firstTag = visitedViews.value[0]
    lastTag = visitedViews.value[visitedViews.value.length - 1]
  }
  if (firstTag === currentTag) {
    $scrollWrapper.scrollLeft = 0
  } else if (lastTag === currentTag) {
    $scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
  } else {
    const tagListDom: any = document.getElementsByClassName('tags-view-item');
    const currentIndex = visitedViews.value.findIndex(item => item === currentTag)
    let prevTag = null
    let nextTag = null
    for (const k in tagListDom) {
      if (k !== 'length' && Object.hasOwnProperty.call(tagListDom, k)) {
        if (tagListDom[k].dataset.path === visitedViews.value[currentIndex - 1].path) {
          prevTag = tagListDom[k];
        }
        if (tagListDom[k].dataset.path === visitedViews.value[currentIndex + 1].path) {
          nextTag = tagListDom[k];
        }
      }
    // find first tag and last tag
    if (visitedViews.value.length > 0) {
        firstTag = visitedViews.value[0]
        lastTag = visitedViews.value[visitedViews.value.length - 1]
    }
    // the tag's offsetLeft after of nextTag
    const afterNextTagOffsetLeft = nextTag.offsetLeft + nextTag.offsetWidth + tagAndTagSpacing.value
    if (firstTag === currentTag) {
        $scrollWrapper.scrollLeft = 0
    } else if (lastTag === currentTag) {
        $scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
    } else {
        const tagListDom: any = document.getElementsByClassName('tags-view-item');
        const currentIndex = visitedViews.value.findIndex(item => item === currentTag)
        let prevTag = null
        let nextTag = null
    // the tag's offsetLeft before of prevTag
    const beforePrevTagOffsetLeft = prevTag.offsetLeft - tagAndTagSpacing.value
    if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
      $scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
    } else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
      $scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
        for (const k in tagListDom) {
            if (k !== 'length' && Object.hasOwnProperty.call(tagListDom, k)) {
                if (tagListDom[k].dataset.path === visitedViews.value[currentIndex - 1].path) {
                    prevTag = tagListDom[k];
                }
                if (tagListDom[k].dataset.path === visitedViews.value[currentIndex + 1].path) {
                    nextTag = tagListDom[k];
                }
            }
        }
        // the tag's offsetLeft after of nextTag
        const afterNextTagOffsetLeft = nextTag.offsetLeft + nextTag.offsetWidth + tagAndTagSpacing.value
        // the tag's offsetLeft before of prevTag
        const beforePrevTagOffsetLeft = prevTag.offsetLeft - tagAndTagSpacing.value
        if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
            $scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
        } else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
            $scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
        }
    }
  }
}
defineExpose({
  moveToTarget,
    moveToTarget,
})
</script>
<template>
  <el-scrollbar ref="scrollContainerRef" :vertical="false" class="scroll-container" @wheel.prevent="handleScroll">
    <slot />
  </el-scrollbar>
</template>
<style lang="scss" scoped>
.scroll-container {
src/layout/index.vue
@@ -1,56 +1,3 @@
<script setup lang="ts">
import SideBar from './components/Sidebar/index.vue'
import { AppMain, Navbar, Settings, TagsView } from './components'
import useAppStore from '@/store/modules/app'
import useSettingsStore from '@/store/modules/settings'
const settingsStore = useSettingsStore()
const theme = computed(() => settingsStore.theme);
const sidebar = computed(() => useAppStore().sidebar);
const device = computed(() => useAppStore().device);
const needTagsView = computed(() => settingsStore.tagsView);
const fixedHeader = computed(() => settingsStore.fixedHeader);
const classObj = computed(() => ({
  hideSidebar: !sidebar.value.opened,
  openSidebar: sidebar.value.opened,
  withoutAnimation: sidebar.value.withoutAnimation,
  mobile: device.value === 'mobile'
}))
const { width } = useWindowSize();
const WIDTH = 992; // refer to Bootstrap's responsive design
watchEffect(() => {
  if (device.value === 'mobile' && sidebar.value.opened) {
    useAppStore().closeSideBar({ withoutAnimation: false })
  }
  if (width.value - 1 < WIDTH) {
    useAppStore().toggleDevice('mobile')
    useAppStore().closeSideBar({ withoutAnimation: true })
  } else {
    useAppStore().toggleDevice('desktop')
  }
})
const navbarRef = ref(Navbar);
const settingRef = ref(Settings);
onMounted(() => {
  nextTick(() => {
    navbarRef.value.initTenantList();
  })
})
const handleClickOutside = () => {
  useAppStore().closeSideBar({ withoutAnimation: false })
}
const setLayout = () => {
  settingRef.value.openSetting();
}
</script>
<template>
  <div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }">
    <div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
@@ -66,6 +13,59 @@
  </div>
</template>
<script setup lang="ts">
import SideBar from './components/Sidebar/index.vue'
import { AppMain, Navbar, Settings, TagsView } from './components'
import useAppStore from '@/store/modules/app'
import useSettingsStore from '@/store/modules/settings'
const settingsStore = useSettingsStore()
const theme = computed(() => settingsStore.theme);
const sidebar = computed(() => useAppStore().sidebar);
const device = computed(() => useAppStore().device);
const needTagsView = computed(() => settingsStore.tagsView);
const fixedHeader = computed(() => settingsStore.fixedHeader);
const classObj = computed(() => ({
    hideSidebar: !sidebar.value.opened,
    openSidebar: sidebar.value.opened,
    withoutAnimation: sidebar.value.withoutAnimation,
    mobile: device.value === 'mobile'
}))
const { width } = useWindowSize();
const WIDTH = 992; // refer to Bootstrap's responsive design
watchEffect(() => {
    if (device.value === 'mobile' && sidebar.value.opened) {
        useAppStore().closeSideBar({ withoutAnimation: false })
    }
    if (width.value - 1 < WIDTH) {
        useAppStore().toggleDevice('mobile')
        useAppStore().closeSideBar({ withoutAnimation: true })
    } else {
        useAppStore().toggleDevice('desktop')
    }
})
const navbarRef = ref(Navbar);
const settingRef = ref(Settings);
onMounted(() => {
    nextTick(() => {
        navbarRef.value.initTenantList();
    })
})
const handleClickOutside = () => {
    useAppStore().closeSideBar({ withoutAnimation: false })
}
const setLayout = () => {
    settingRef.value.openSetting();
}
</script>
<style lang="scss" scoped>
  @import "@/assets/styles/mixin.scss";
  @import "@/assets/styles/variables.module.scss";
src/views/monitor/cache/index.vue
@@ -1,67 +1,3 @@
<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();
const usedmemory = ref();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const getList = async () => {
  proxy?.$modal.loading("正在加载缓存监控数据,请稍候!");
  const res = await getCache();
  proxy?.$modal.closeLoading();
  cache.value = res.data;
  const commandstatsIntance = echarts.init(commandstats.value, "macarons");
  commandstatsIntance.setOption({
    tooltip: {
      trigger: "item",
      formatter: "{a} <br/>{b} : {c} ({d}%)"
    },
    series: [
      {
        name: "命令",
        type: "pie",
        roseType: "radius",
        radius: [15, 95],
        center: ["50%", "38%"],
        data: res.data.commandStats,
        animationEasing: "cubicInOut",
        animationDuration: 1000
      }
    ]
  });
  const usedmemoryInstance = echarts.init(usedmemory.value, "macarons");
  usedmemoryInstance.setOption({
    tooltip: {
      formatter: "{b} <br/>{a} : " + cache.value.info.used_memory_human
    },
    series: [
      {
        name: "峰值",
        type: "gauge",
        min: 0,
        max: 1000,
        detail: {
          formatter: cache.value.info.used_memory_human
        },
        data: [
          {
            value: parseFloat(cache.value.info.used_memory_human),
            name: "内存消耗"
          }
        ]
      }
    ]
  })
}
onMounted(() => {
  getList();
})
</script>
<template>
  <div class="p-2">
    <el-row>
@@ -186,3 +122,68 @@
    </el-row>
  </div>
</template>
<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();
const usedmemory = ref();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const getList = async () => {
    proxy?.$modal.loading("正在加载缓存监控数据,请稍候!");
    const res = await getCache();
    proxy?.$modal.closeLoading();
    cache.value = res.data;
    const commandstatsIntance = echarts.init(commandstats.value, "macarons");
    commandstatsIntance.setOption({
        tooltip: {
            trigger: "item",
            formatter: "{a} <br/>{b} : {c} ({d}%)"
        },
        series: [
            {
                name: "命令",
                type: "pie",
                roseType: "radius",
                radius: [15, 95],
                center: ["50%", "38%"],
                data: res.data.commandStats,
                animationEasing: "cubicInOut",
                animationDuration: 1000
            }
        ]
    });
    const usedmemoryInstance = echarts.init(usedmemory.value, "macarons");
    usedmemoryInstance.setOption({
        tooltip: {
            formatter: "{b} <br/>{a} : " + cache.value.info.used_memory_human
        },
        series: [
            {
                name: "峰值",
                type: "gauge",
                min: 0,
                max: 1000,
                detail: {
                    formatter: cache.value.info.used_memory_human
                },
                data: [
                    {
                        value: parseFloat(cache.value.info.used_memory_human),
                        name: "内存消耗"
                    }
                ]
            }
        ]
    })
}
onMounted(() => {
    getList();
})
</script>
src/views/system/config/index.vue
@@ -1,143 +1,3 @@
<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"));
const configList = ref<ConfigVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<number | string>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const queryFormRef = ref(ElForm);
const configFormRef = ref(ElForm);
const dialog = reactive<DialogOption>({
  visible: false,
  title: ''
});
const initFormData: ConfigForm = {
  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" }]
  }
});
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;
}
/** 取消按钮 */
const cancel = () => {
  reset();
  dialog.visible = false;
}
/** 表单重置 */
const reset = () => {
  form.value = {...initFormData};
  configFormRef.value.resetFields();
}
/** 搜索按钮操作 */
const handleQuery = () => {
  queryParams.value.pageNum = 1;
  getList();
}
/** 重置按钮操作 */
const resetQuery = () => {
  dateRange.value = ['', ''];
  queryFormRef.value.resetFields();
  handleQuery();
}
/** 多选框选中数据 */
const handleSelectionChange = (selection: ConfigVO[]) => {
  ids.value = selection.map(item => item.configId);
  single.value = selection.length != 1;
  multiple.value = !selection.length;
}
/** 新增按钮操作 */
const handleAdd = () => {
  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;
  })
}
/** 提交按钮 */
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();
    }
  });
}
/** 删除按钮操作 */
const handleDelete = async (row?: ConfigVO) => {
  const configIds = row?.configId || ids.value;
  await proxy?.$modal.confirm('是否确认删除参数编号为"' + configIds + '"的数据项?');
  await delConfig(configIds);
  getList();
  proxy?.$modal.msgSuccess("删除成功");
}
/** 导出按钮操作 */
const handleExport = () => {
  proxy?.download("system/config/export", {
    ...queryParams.value
  }, `config_${new Date().getTime()}.xlsx`);
}
/** 刷新缓存按钮操作 */
const handleRefreshCache = async () => {
  await refreshCache();
  proxy?.$modal.msgSuccess("刷新缓存成功");
}
onMounted(() => {
  getList();
})
</script>
<template>
  <div class="p-2">
    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
@@ -259,3 +119,143 @@
    </el-dialog>
  </div>
</template>
<script setup name="Config" lang="ts">
import { listConfig, getConfig, delConfig, addConfig, updateConfig, refreshCache } from "@/api/system/config";
import { ConfigForm, ConfigQuery, ConfigVO } from "@/api/system/config/types";
import { ComponentInternalInstance } from "vue";
import { DateModelType } from 'element-plus';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_yes_no } = toRefs<any>(proxy?.useDict("sys_yes_no"));
const configList = ref<ConfigVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<number | string>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const queryFormRef = ref(ElForm);
const configFormRef = ref(ElForm);
const dialog = reactive<DialogOption>({
    visible: false,
    title: ''
});
const initFormData: ConfigForm = {
    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" }]
    }
});
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;
}
/** 取消按钮 */
const cancel = () => {
    reset();
    dialog.visible = false;
}
/** 表单重置 */
const reset = () => {
    form.value = {...initFormData};
    configFormRef.value.resetFields();
}
/** 搜索按钮操作 */
const handleQuery = () => {
    queryParams.value.pageNum = 1;
    getList();
}
/** 重置按钮操作 */
const resetQuery = () => {
    dateRange.value = ['', ''];
    queryFormRef.value.resetFields();
    handleQuery();
}
/** 多选框选中数据 */
const handleSelectionChange = (selection: ConfigVO[]) => {
    ids.value = selection.map(item => item.configId);
    single.value = selection.length != 1;
    multiple.value = !selection.length;
}
/** 新增按钮操作 */
const handleAdd = () => {
    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;
    })
}
/** 提交按钮 */
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();
        }
    });
}
/** 删除按钮操作 */
const handleDelete = async (row?: ConfigVO) => {
    const configIds = row?.configId || ids.value;
    await proxy?.$modal.confirm('是否确认删除参数编号为"' + configIds + '"的数据项?');
    await delConfig(configIds);
    getList();
    proxy?.$modal.msgSuccess("删除成功");
}
/** 导出按钮操作 */
const handleExport = () => {
    proxy?.download("system/config/export", {
        ...queryParams.value
    }, `config_${new Date().getTime()}.xlsx`);
}
/** 刷新缓存按钮操作 */
const handleRefreshCache = async () => {
    await refreshCache();
    proxy?.$modal.msgSuccess("刷新缓存成功");
}
onMounted(() => {
    getList();
})
</script>
src/views/system/user/profile/index.vue
@@ -1,31 +1,3 @@
<script setup name="Profile" lang="ts">
import userAvatar from "./userAvatar.vue";
import userInfo from "./userInfo.vue";
import resetPwd from "./resetPwd.vue";
import { getUserProfile } from "@/api/system/user";
const activeTab = ref("userinfo");
const state = ref<{ user: any; roleGroup: string;  postGroup: string}>({
  user: {},
  roleGroup: '',
  postGroup: ''
});
const userForm = ref({});
const getUser = async () => {
  const res = await getUserProfile();
  state.value.user = res.data.user;
  userForm.value = { ...res.data.user }
  state.value.roleGroup = res.data.roleGroup;
  state.value.postGroup = res.data.postGroup;
};
onMounted(() => {
  getUser();
})
</script>
<template>
  <div class="p-2">
    <el-row :gutter="20">
@@ -89,3 +61,31 @@
    </el-row>
  </div>
</template>
<script setup name="Profile" lang="ts">
import userAvatar from "./userAvatar.vue";
import userInfo from "./userInfo.vue";
import resetPwd from "./resetPwd.vue";
import { getUserProfile } from "@/api/system/user";
const activeTab = ref("userinfo");
const state = ref<{ user: any; roleGroup: string;  postGroup: string}>({
    user: {},
    roleGroup: '',
    postGroup: ''
});
const userForm = ref({});
const getUser = async () => {
    const res = await getUserProfile();
    state.value.user = res.data.user;
    userForm.value = { ...res.data.user }
    state.value.roleGroup = res.data.roleGroup;
    state.value.postGroup = res.data.postGroup;
};
onMounted(() => {
    getUser();
})
</script>
src/views/system/user/profile/resetPwd.vue
@@ -1,48 +1,3 @@
<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';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const pwdRef = ref(ElForm);
const user = ref<ResetPwdForm>({
  oldPassword: '',
  newPassword: '',
  confirmPassword: ''
});
const equalToPassword = (rule: any, value: string, callback: any) => {
  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" }]
});
/** 提交按钮 */
const submit = () => {
  pwdRef.value.validate(async (valid: boolean) => {
    if (valid) {
      await updateUserPwd(user.value.oldPassword, user.value.newPassword)
      proxy?.$modal.msgSuccess("修改成功");
    }
  });
};
/** 关闭按钮 */
const close = () => {
  proxy?.$tab.closePage();
};
</script>
<template>
  <el-form ref="pwdRef" :model="user" :rules="rules" label-width="80px">
    <el-form-item label="旧密码" prop="oldPassword">
@@ -60,3 +15,48 @@
    </el-form-item>
  </el-form>
</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';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const pwdRef = ref(ElForm);
const user = ref<ResetPwdForm>({
    oldPassword: '',
    newPassword: '',
    confirmPassword: ''
});
const equalToPassword = (rule: any, value: string, callback: any) => {
    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" }]
});
/** 提交按钮 */
const submit = () => {
    pwdRef.value.validate(async (valid: boolean) => {
        if (valid) {
            await updateUserPwd(user.value.oldPassword, user.value.newPassword)
            proxy?.$modal.msgSuccess("修改成功");
        }
    });
};
/** 关闭按钮 */
const close = () => {
    proxy?.$tab.closePage();
};
</script>
src/views/system/user/profile/userAvatar.vue
@@ -1,104 +1,3 @@
<script setup lang="ts">
import "vue-cropper/dist/index.css";
import { VueCropper } from "vue-cropper";
import { uploadAvatar } from "@/api/system/user";
import useUserStore from "@/store/modules/user";
import { 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
}
const userStore = useUserStore();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const open = ref(false);
const visible = ref(false);
const title = ref("修改头像");
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
});
/** 编辑头像 */
const editCropper = () => {
  open.value = true;
}
/** 打开弹出层结束时的回调 */
const modalOpened = () => {
  visible.value = true;
}
/** 覆盖默认上传行为 */
const requestUpload = (): any => {}
/** 向左旋转 */
const rotateLeft = () => {
  cropper.value.rotateLeft();
}
/** 向右旋转 */
const rotateRight = () => {
  cropper.value.rotateRight();
}
/** 图片缩放 */
const changeScale = (num: number) => {
  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;
    };
  }
}
/** 上传图片 */
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;
  });
}
/** 实时预览 */
const realTime = (data: any) => {
  options.previews = data;
}
/** 关闭窗口 */
const closeDialog = () => {
  options.img = userStore.avatar;
  options.visible = false;
}
</script>
<template>
  <div class="user-info-head" @click="editCropper()">
    <img :src="options.img as string" title="点击上传头像" class="img-circle img-lg" />
@@ -154,6 +53,107 @@
  </div>
</template>
<script setup lang="ts">
import "vue-cropper/dist/index.css";
import { VueCropper } from "vue-cropper";
import { uploadAvatar } from "@/api/system/user";
import useUserStore from "@/store/modules/user";
import { 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
}
const userStore = useUserStore();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const open = ref(false);
const visible = ref(false);
const title = ref("修改头像");
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
});
/** 编辑头像 */
const editCropper = () => {
    open.value = true;
}
/** 打开弹出层结束时的回调 */
const modalOpened = () => {
    visible.value = true;
}
/** 覆盖默认上传行为 */
const requestUpload = (): any => {}
/** 向左旋转 */
const rotateLeft = () => {
    cropper.value.rotateLeft();
}
/** 向右旋转 */
const rotateRight = () => {
    cropper.value.rotateRight();
}
/** 图片缩放 */
const changeScale = (num: number) => {
    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;
        };
    }
}
/** 上传图片 */
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;
    });
}
/** 实时预览 */
const realTime = (data: any) => {
    options.previews = data;
}
/** 关闭窗口 */
const closeDialog = () => {
    options.img = userStore.avatar;
    options.visible = false;
}
</script>
<style lang="scss" scoped>
.user-info-head {
  position: relative;
src/views/system/user/profile/userInfo.vue
@@ -1,43 +1,3 @@
<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>,
  }
});
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 submit = () => {
  userRef.value.validate(async (valid: boolean) => {
    if (valid) {
      await updateUserProfile(props.user)
      proxy?.$modal.msgSuccess("修改成功");
    }
  });
};
/** 关闭按钮 */
const close = () => {
  proxy?.$tab.closePage();
};
</script>
<template>
  <el-form ref="userRef" :model="userForm" :rules="rules" label-width="80px">
    <el-form-item label="用户昵称" prop="nickName">
@@ -61,3 +21,43 @@
    </el-form-item>
  </el-form>
</template>
<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>,
    }
});
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 submit = () => {
    userRef.value.validate(async (valid: boolean) => {
        if (valid) {
            await updateUserProfile(props.user)
            proxy?.$modal.msgSuccess("修改成功");
        }
    });
};
/** 关闭按钮 */
const close = () => {
    proxy?.$tab.closePage();
};
</script>
src/views/tool/gen/basicInfoForm.vue
@@ -1,26 +1,3 @@
<script setup lang="ts">
import { PropType } from 'vue';
const prop = defineProps({
  info: {
    type: Object as PropType<any>,
    default: () => {
      return {};
    }
  }
});
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" }]
});
</script>
<template>
  <el-form ref="basicInfoForm" :model="infoForm" :rules="rules" label-width="150px">
    <el-row>
@@ -52,3 +29,26 @@
    </el-row>
  </el-form>
</template>
<script setup lang="ts">
import { PropType } from 'vue';
const prop = defineProps({
    info: {
        type: Object as PropType<any>,
        default: () => {
            return {};
        }
    }
});
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" }]
});
</script>
src/views/tool/gen/genInfoForm.vue
@@ -1,76 +1,3 @@
<script setup lang="ts">
import { listMenu } from '@/api/system/menu';
import { ComponentInternalInstance, PropType } from 'vue';
interface MenuOptionsType {
  menuId: number;
  menuName: string;
  children: MenuOptionsType[] | undefined;
}
const subColumns = ref<any>([]);
const menuOptions = ref<Array<MenuOptionsType>>([]);
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const props = defineProps({
  info: {
    type: Object as PropType<any>,
    default: null
  },
  tables: {
    type: Array as PropType<any[]>,
    default: null
  }
});
const infoForm = computed(() => props.info);
const table = computed(() => props.tables);
// 表单校验
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" }]
});
const subSelectChange = () => {
  infoForm.value.subTableFkName = "";
}
const tplSelectChange = (value: string) => {
  if (value !== "sub") {
    infoForm.value.subTableName = "";
    infoForm.value.subTableFkName = "";
  }
}
const setSubTableColumns = (value: string) => {
  table.value.forEach(item => {
    const name = item.tableName;
    if (value === name) {
      subColumns.value = item.columns;
      return;
    }
  })
}
/** 查询菜单下拉树结构 */
const getMenuTreeselect = async () => {
  const res = await listMenu();
  const data = proxy?.handleTree<MenuOptionsType>(res.data, "menuId");
  if (data) {
    menuOptions.value = data
  }
}
watch(() => props.info.subTableName, val => {
  setSubTableColumns(val);
});
onMounted(() => {
  getMenuTreeselect();
})
</script>
<template>
  <el-form ref="genInfoForm" :model="infoForm" :rules="rules" label-width="150px">
    <el-row>
@@ -287,3 +214,76 @@
    </template>
  </el-form>
</template>
<script setup lang="ts">
import { listMenu } from '@/api/system/menu';
import { ComponentInternalInstance, PropType } from 'vue';
interface MenuOptionsType {
    menuId: number;
    menuName: string;
    children: MenuOptionsType[] | undefined;
}
const subColumns = ref<any>([]);
const menuOptions = ref<Array<MenuOptionsType>>([]);
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const props = defineProps({
    info: {
        type: Object as PropType<any>,
        default: null
    },
    tables: {
        type: Array as PropType<any[]>,
        default: null
    }
});
const infoForm = computed(() => props.info);
const table = computed(() => props.tables);
// 表单校验
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" }]
});
const subSelectChange = () => {
    infoForm.value.subTableFkName = "";
}
const tplSelectChange = (value: string) => {
    if (value !== "sub") {
        infoForm.value.subTableName = "";
        infoForm.value.subTableFkName = "";
    }
}
const setSubTableColumns = (value: string) => {
    table.value.forEach(item => {
        const name = item.tableName;
        if (value === name) {
            subColumns.value = item.columns;
            return;
        }
    })
}
/** 查询菜单下拉树结构 */
const getMenuTreeselect = async () => {
    const res = await listMenu();
    const data = proxy?.handleTree<MenuOptionsType>(res.data, "menuId");
    if (data) {
        menuOptions.value = data
    }
}
watch(() => props.info.subTableName, val => {
    setSubTableColumns(val);
});
onMounted(() => {
    getMenuTreeselect();
})
</script>