| | |
| | | <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"> |
| | |
| | | </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; |
| | |
| | | <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" /> |
| | |
| | | </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; |
| | |
| | | <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="点击选择图标"> |
| | |
| | | </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; |
| | |
| | | <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> |
| | |
| | | </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; |
| | |
| | | <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"> |
| | |
| | | </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; |
| | |
| | | <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 { |
| | |
| | | <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"> |
| | |
| | | <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: '' |
| | | } |
| | | }) |
| | | |
| | |
| | | const valueId = computed({ |
| | | get: () => props.value, |
| | | set: (val) => { |
| | | emit('update:value', val) |
| | | emit('update:value', val) |
| | | } |
| | | }); |
| | | const valueTitle = ref(''); |
| | |
| | | |
| | | 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) { |
| | |
| | | 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> |
| | |
| | | <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: { |
| | |
| | | }; |
| | | }) |
| | | </script> |
| | | |
| | | <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"> |
| | | 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 |
| | |
| | | ></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> |
| | |
| | | <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"> |
| | |
| | | </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; |
| | |
| | | <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> |
| | |
| | | <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" |
| | |
| | | </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; |
| | |
| | | <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' |
| | |
| | | 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> |
| | |
| | | <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'; |
| | |
| | | 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 { |
| | |
| | | <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" /> |
| | |
| | | </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"; |
| | |
| | | <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> |
| | |
| | | </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> |
| | |
| | | <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"> |
| | |
| | | </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> |
| | |
| | | <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"> |
| | |
| | | </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> |
| | |
| | | <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"> |
| | |
| | | </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> |
| | |
| | | <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" /> |
| | |
| | | </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; |
| | |
| | | <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"> |
| | |
| | | </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> |
| | |
| | | <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> |
| | |
| | | </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> |
| | |
| | | <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> |
| | |
| | | </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> |