提交
This commit is contained in:
parent
7423baea13
commit
b2b8ddf114
@ -21,3 +21,42 @@ export function getMenuList(params?) {
|
||||
params,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 获取菜单详情
|
||||
* @param params
|
||||
*/
|
||||
export function getMenuDetail(id) {
|
||||
return http.request({
|
||||
url: '/menu/detail/'+id,
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @description: 添加菜单
|
||||
*/
|
||||
export function menuAdd(data:any) {
|
||||
return http.request({
|
||||
url: '/menu/add',
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @description: 更新菜单
|
||||
*/
|
||||
export function menuUpdate(data:any) {
|
||||
return http.request({
|
||||
url: '/menu/update',
|
||||
method: 'PUT',
|
||||
data
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @description: 删除菜单
|
||||
*/
|
||||
export function menuDelete(id) {
|
||||
return http.request({
|
||||
url: '/menu/delete/'+id,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
@ -87,3 +87,50 @@ export function getUserList(params) {
|
||||
params,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @description: 根据ID获取详情
|
||||
*/
|
||||
export function getUserDetail(userId) {
|
||||
return http.request({
|
||||
url: '/user/detail/'+userId,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @description: 添加用户
|
||||
*/
|
||||
export function userAdd(data:any) {
|
||||
return http.request({
|
||||
url: '/user/add',
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @description: 更新用户
|
||||
*/
|
||||
export function userUpdate(data:any) {
|
||||
return http.request({
|
||||
url: '/user/update',
|
||||
method: 'PUT',
|
||||
data
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @description: 删除用户
|
||||
*/
|
||||
export function userDelete(userId) {
|
||||
return http.request({
|
||||
url: '/user/delete/'+userId,
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @description: 批量删除用户
|
||||
*/
|
||||
export function userBatchDelete(userId) {
|
||||
return http.request({
|
||||
url: '/user/batchDelete/'+userId,
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
13
src/components/icon/index.ts
Normal file
13
src/components/icon/index.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import * as ElementPlusIcons from '@element-plus/icons-vue'
|
||||
|
||||
export const EL_ICON_PREFIX = 'el-icon-'
|
||||
|
||||
const elIconsName: string[] = []
|
||||
|
||||
for (const [, component] of Object.entries(ElementPlusIcons)) {
|
||||
elIconsName.push(`${EL_ICON_PREFIX}${component.name}`)
|
||||
}
|
||||
|
||||
export function getElementPlusIconNames() {
|
||||
return elIconsName
|
||||
}
|
37
src/components/icon/index.vue
Normal file
37
src/components/icon/index.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<script lang="ts">
|
||||
import {createVNode, defineComponent, resolveComponent} from "vue";
|
||||
import { ElIcon } from 'element-plus'
|
||||
import { EL_ICON_PREFIX } from './index'
|
||||
import svgIcon from './svg-icon.vue'
|
||||
export default defineComponent({
|
||||
name: 'Icon',
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: '14px'
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: 'inherit'
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
if (props.name.indexOf(EL_ICON_PREFIX) === 0) {
|
||||
// el-icon
|
||||
return () =>
|
||||
createVNode(
|
||||
ElIcon,
|
||||
{
|
||||
size: props.size,
|
||||
color: props.color
|
||||
},
|
||||
() => [createVNode(resolveComponent(props.name.replace(EL_ICON_PREFIX, '')))]
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
182
src/components/icon/picker.vue
Normal file
182
src/components/icon/picker.vue
Normal file
@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<div class="icon-select">
|
||||
<el-popover
|
||||
trigger="contextmenu"
|
||||
v-model:visible="state.popoverVisible"
|
||||
:width="state.popoverWidth"
|
||||
>
|
||||
<div
|
||||
@mouseover.stop="state.mouseoverSelect = true"
|
||||
@mouseout.stop="state.mouseoverSelect = false"
|
||||
>
|
||||
<div>
|
||||
<div class="flex justify-between">
|
||||
<div class="mb-3">请选择图标</div>
|
||||
<div>
|
||||
<span
|
||||
v-for="(item, index) in iconTabsMap"
|
||||
:key="index"
|
||||
class="cursor-pointer text-sm ml-2"
|
||||
:class="{
|
||||
'text-primary': index == tabIndex
|
||||
}"
|
||||
@click="tabIndex = index"
|
||||
>
|
||||
{{ item.name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="height: 280px">
|
||||
<el-scrollbar>
|
||||
<div class="flex flex-wrap">
|
||||
<div v-for="item in iconNamesFliter" :key="item" style="margin: 4px">
|
||||
<el-button @click="handleSelect(item)">
|
||||
<icon :name="item" :size="18" />
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-input
|
||||
ref="inputRef"
|
||||
v-model.trim="state.inputValue"
|
||||
placeholder="搜索图标"
|
||||
:autofocus="false"
|
||||
:disabled="disabled"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
clearable
|
||||
>
|
||||
<template #prepend>
|
||||
<div class="flex items-center" v-if="modelValue">
|
||||
<el-tooltip class="flex-1 w-20" :content="modelValue" placement="top">
|
||||
<icon
|
||||
class="mr-1"
|
||||
:key="modelValue"
|
||||
:name="modelValue"
|
||||
:size="16"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
|
||||
<template v-else>无</template>
|
||||
</template>
|
||||
<template #append>
|
||||
<el-button>
|
||||
<icon name="el-icon-Close" :size="18" @click="handleClear" />
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick, onMounted, reactive, shallowRef, watch,ref } from 'vue'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
import { ElInput } from 'element-plus'
|
||||
import { getElementPlusIconNames } from './index'
|
||||
import icon from './index.vue';
|
||||
interface Props {
|
||||
modelValue: string
|
||||
disabled?: boolean
|
||||
}
|
||||
withDefaults(defineProps<Props>(), {
|
||||
modelValue: '',
|
||||
disabled: false
|
||||
})
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'update:modelValue', value: string): void
|
||||
(e: 'change', value: string): void
|
||||
}>()
|
||||
|
||||
const tabIndex = ref(0)
|
||||
const iconTabsMap = [
|
||||
{
|
||||
name: 'element图标',
|
||||
icons: getElementPlusIconNames()
|
||||
}
|
||||
]
|
||||
|
||||
const inputRef = shallowRef<InstanceType<typeof ElInput>>()
|
||||
|
||||
const state = reactive({
|
||||
inputValue: '',
|
||||
popoverVisible: false,
|
||||
popoverWidth: 0,
|
||||
mouseoverSelect: false,
|
||||
inputFocus: false
|
||||
})
|
||||
|
||||
// input 框聚焦
|
||||
const handleFocus = () => {
|
||||
state.inputFocus = state.popoverVisible = true
|
||||
}
|
||||
|
||||
// input 框失去焦点
|
||||
const handleBlur = () => {
|
||||
state.inputFocus = false
|
||||
state.popoverVisible = state.mouseoverSelect
|
||||
}
|
||||
|
||||
// 选中图标
|
||||
const handleSelect = (icon: string) => {
|
||||
state.mouseoverSelect = state.popoverVisible = false
|
||||
emits('update:modelValue', icon)
|
||||
emits('change', icon)
|
||||
}
|
||||
//取消选中
|
||||
const handleClear = () => {
|
||||
emits('update:modelValue', '')
|
||||
emits('change', '')
|
||||
}
|
||||
|
||||
//根据输入框内容塞选
|
||||
const iconNamesFliter = computed(() => {
|
||||
const iconNames = iconTabsMap[tabIndex.value]?.icons ?? []
|
||||
if (!state.inputValue) {
|
||||
return iconNames
|
||||
}
|
||||
const inputValue = state.inputValue.toLowerCase()
|
||||
return iconNames.filter((icon: string) => {
|
||||
if (icon.toLowerCase().indexOf(inputValue) !== -1) {
|
||||
return icon
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 获取 input 的宽度
|
||||
const getInputWidth = () => {
|
||||
nextTick(() => {
|
||||
const inputWidth = inputRef.value?.$el.offsetWidth
|
||||
state.popoverWidth = inputWidth < 300 ? 300 : inputWidth
|
||||
})
|
||||
}
|
||||
|
||||
//监听body点击事件
|
||||
useEventListener(document.body, 'click', () => {
|
||||
state.popoverVisible = state.inputFocus || state.mouseoverSelect ? true : false
|
||||
})
|
||||
|
||||
watch(
|
||||
() => state.popoverVisible,
|
||||
async (value) => {
|
||||
await nextTick()
|
||||
if (value) {
|
||||
inputRef.value?.focus()
|
||||
} else {
|
||||
inputRef.value?.blur()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
getInputWidth()
|
||||
})
|
||||
</script>
|
39
src/components/icon/svg-icon.vue
Normal file
39
src/components/icon/svg-icon.vue
Normal file
@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<svg aria-hidden="true" :style="styles">
|
||||
<use :xlink:href="symbolId" fill="currentColor" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { addUnit } from '@/util/util'
|
||||
import type { CSSProperties } from 'vue'
|
||||
import {computed, defineComponent} from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: 16
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: 'inherit'
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const symbolId = computed(() => `#${props.name}`)
|
||||
const styles = computed<CSSProperties>(() => {
|
||||
return {
|
||||
width: addUnit(props.size),
|
||||
height: addUnit(props.size),
|
||||
color: props.color
|
||||
}
|
||||
})
|
||||
return { symbolId, styles }
|
||||
}
|
||||
})
|
||||
</script>
|
@ -1,19 +1,23 @@
|
||||
import { ObjectDirective } from 'vue';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
|
||||
export const permission: ObjectDirective = {
|
||||
export const perm: ObjectDirective = {
|
||||
mounted(el: HTMLButtonElement, binding) {
|
||||
if (binding.value == undefined) return;
|
||||
const { action, effect } = binding.value;
|
||||
const { hasPermission } = usePermission();
|
||||
if (!hasPermission(action)) {
|
||||
if (effect == 'disabled') {
|
||||
el.disabled = true;
|
||||
el.style['disabled'] = 'disabled';
|
||||
el.classList.add('is-disabled');
|
||||
} else {
|
||||
el.remove();
|
||||
}
|
||||
const { hasSomePermission } = usePermission();
|
||||
if (!hasSomePermission(binding.value)) {
|
||||
el.parentNode.removeChild(el)
|
||||
}
|
||||
},
|
||||
};
|
||||
export const perms: ObjectDirective = {
|
||||
mounted(el: HTMLButtonElement, binding) {
|
||||
if (binding.value == undefined) return;
|
||||
const { action, effect } = binding.value;
|
||||
const { hasEveryPermission } = usePermission();
|
||||
if (!hasEveryPermission(binding.value)) {
|
||||
el.parentNode.removeChild(el)
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -30,7 +30,7 @@ export function usePermission() {
|
||||
function hasEveryPermission(accesses: string[]): boolean {
|
||||
const permissionsList = userStore.getPermissions;
|
||||
if (Array.isArray(accesses)) {
|
||||
return permissionsList.every((access: any) => accesses.includes(access.value));
|
||||
return accesses.every((access: any) => permissionsList.includes(access));
|
||||
}
|
||||
throw new Error(`[hasEveryPermission]: ${accesses} should be a array !`);
|
||||
}
|
||||
@ -43,7 +43,7 @@ export function usePermission() {
|
||||
function hasSomePermission(accesses: string[]): boolean {
|
||||
const permissionsList = userStore.getPermissions;
|
||||
if (Array.isArray(accesses)) {
|
||||
return permissionsList.some((access: any) => accesses.includes(access.value));
|
||||
return accesses.some((access: any) => permissionsList.includes(access));
|
||||
}
|
||||
throw new Error(`[hasSomePermission]: ${accesses} should be a array !`);
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import './styles/index.scss';
|
||||
import 'element-plus/theme-chalk/display.css';
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css';
|
||||
import 'nprogress/nprogress.css';
|
||||
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
import router, { setupRouter } from './router';
|
||||
@ -12,8 +12,10 @@ import { setupStore } from '@/store';
|
||||
import { setupElement, setupDirectives, setupCustomComponents } from '@/plugins';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = createApp(App);
|
||||
|
||||
const app=createApp(App)
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component)
|
||||
}
|
||||
// 全局完整引入 element 组件
|
||||
setupElement(app);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { App } from 'vue';
|
||||
|
||||
import { permission } from '@/directives/permission';
|
||||
import { perm,perms } from '@/directives/permission';
|
||||
import { scrollBar } from '@/directives/scrollBar';
|
||||
|
||||
/**
|
||||
@ -8,6 +8,7 @@ import { scrollBar } from '@/directives/scrollBar';
|
||||
* @param app
|
||||
*/
|
||||
export function setupDirectives(app: App) {
|
||||
app.directive('permission', permission); // 权限控制指令(演示)
|
||||
app.directive('perm', perm); // 权限控制指令 (是否包含其中某个权限)
|
||||
app.directive('perms', perms); // 权限控制指令 (是否包含所有权限)
|
||||
app.directive('scrollBar', scrollBar); // 滚动条
|
||||
}
|
||||
|
@ -8,6 +8,11 @@ const modules: any = import.meta.glob('./modules/**/*.ts', { eager: true });
|
||||
|
||||
const routeModuleList: RouteRecordRaw[] = [];
|
||||
|
||||
const modulesVue = import.meta.glob('/src/views/**/*.vue')
|
||||
export function getModulesKey() {
|
||||
return Object.keys(modulesVue).map((item) => item.replace('/src/views/', '').replace('.vue', ''))
|
||||
}
|
||||
|
||||
Object.keys(modules).forEach((key) => {
|
||||
const mod = modules[key].default || {};
|
||||
const modList = Array.isArray(mod) ? [...mod] : [mod];
|
||||
|
233
src/utils/auth.ts
Normal file
233
src/utils/auth.ts
Normal file
@ -0,0 +1,233 @@
|
||||
import {isObject} from "@vue/shared";
|
||||
import {cloneDeep} from "lodash";
|
||||
import type {RouteLocationNormalizedLoaded} from "vue-router";
|
||||
import {RouteLocationNormalized, useRoute} from "vue-router";
|
||||
import {watch} from "vue";
|
||||
import {
|
||||
ElLoading,
|
||||
ElMessage,
|
||||
ElMessageBox
|
||||
} from "element-plus";
|
||||
import type {LoadingInstance} from "element-plus/es/components/loading/src/loading";
|
||||
|
||||
|
||||
export function isExternal(path: string) {
|
||||
return /^(https?:|mailto:|tel:)/.test(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取正确的路经
|
||||
* @param {String} path 数据
|
||||
*/
|
||||
export function getNormalPath(path: string) {
|
||||
if (path.length === 0 || !path || path == "undefined") {
|
||||
return path;
|
||||
}
|
||||
const newPath = path.replace("//", "/");
|
||||
const length = newPath.length;
|
||||
if (newPath[length - 1] === "/") {
|
||||
return newPath.slice(0, length - 1);
|
||||
}
|
||||
return newPath;
|
||||
}
|
||||
|
||||
export const isEmpty = (value: unknown) => {
|
||||
return value == null && typeof value == "undefined";
|
||||
};
|
||||
|
||||
/**
|
||||
* @description对象格式化为Query语法
|
||||
* @param { Object } params
|
||||
* @return {string} Query语法
|
||||
*/
|
||||
export function objectToQuery(params: Record<string, any>): string {
|
||||
let query = "";
|
||||
for (const props of Object.keys(params)) {
|
||||
const value = params[props];
|
||||
const part = encodeURIComponent(props) + "=";
|
||||
if (!isEmpty(value)) {
|
||||
if (isObject(value)) {
|
||||
for (const key of Object.keys(value)) {
|
||||
if (!isEmpty(value[key])) {
|
||||
const params = props + "[" + key + "]";
|
||||
const subPart = encodeURIComponent(params) + "=";
|
||||
query += subPart + encodeURIComponent(value[key]) + "&";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
query += part + encodeURIComponent(value) + "&";
|
||||
}
|
||||
}
|
||||
}
|
||||
return query.slice(0, -1);
|
||||
}
|
||||
|
||||
export const addUnit = (value: string | number, unit = "px") => {
|
||||
return !Object.is(Number(value), NaN) ? `${value}${unit}` : value;
|
||||
};
|
||||
|
||||
|
||||
export function useWatchRoute(callback: (route: RouteLocationNormalizedLoaded) => void) {
|
||||
const route = useRoute();
|
||||
watch(
|
||||
route,
|
||||
() => {
|
||||
callback(route);
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
return {
|
||||
route
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// declare type MessageType = '' | 'success' | 'warning' | 'info' | 'error';
|
||||
|
||||
export function confirm(msg: string, type: "warning") {
|
||||
return ElMessageBox.confirm(msg, "温馨提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: type
|
||||
});
|
||||
}
|
||||
|
||||
export function message(msg: string, type: string = "success") {
|
||||
ElMessage[type](msg);
|
||||
}
|
||||
|
||||
let loadingInstance = null;
|
||||
|
||||
// 打开全局loading
|
||||
export function loading(msg: string) {
|
||||
loadingInstance = ElLoading.service({
|
||||
lock: true,
|
||||
text: msg
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭全局loading
|
||||
export function closeLoading() {
|
||||
loadingInstance?.close();
|
||||
}
|
||||
|
||||
export const arrayToTree = (
|
||||
data: any[],
|
||||
props = {id: "id", parentId: "pid", children: "children"}
|
||||
) => {
|
||||
data = cloneDeep(data);
|
||||
const {id, parentId, children} = props;
|
||||
const result: any[] = [];
|
||||
const map = new Map();
|
||||
data.forEach((item) => {
|
||||
map.set(item[id], item);
|
||||
const parent = map.get(item[parentId]);
|
||||
if (parent) {
|
||||
parent[children] = parent[children] ?? [];
|
||||
parent[children].push(item);
|
||||
} else {
|
||||
result.push(item);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 树转数组,队列实现广度优先遍历
|
||||
* @param {Array} data 数据
|
||||
* @param {Object} props `{ children: 'children' }`
|
||||
*/
|
||||
|
||||
export const treeToArray = (data: any[], props = {children: "children"}) => {
|
||||
data = cloneDeep(data);
|
||||
const {children} = props;
|
||||
const newData = [];
|
||||
const queue: any[] = [];
|
||||
data.forEach((child: any) => queue.push(child));
|
||||
while (queue.length) {
|
||||
const item: any = queue.shift();
|
||||
if (item[children]) {
|
||||
item[children].forEach((child: any) => queue.push(child));
|
||||
delete item[children];
|
||||
}
|
||||
newData.push(item);
|
||||
}
|
||||
return newData;
|
||||
};
|
||||
/**
|
||||
* 数组转树型结构
|
||||
*/
|
||||
export const buildTree =(array)=> {
|
||||
const tree = {}; // 用于存储树形结构的临时对象
|
||||
const result = []; // 最终的树形结构数组
|
||||
|
||||
array.forEach((item) => {
|
||||
tree[item.id] = { ...item, children: [] }; // 初始化树节点
|
||||
});
|
||||
|
||||
array.forEach((item) => {
|
||||
if (item.parentId === 0) {
|
||||
result.push(tree[item.id]); // 根节点直接添加到结果数组
|
||||
} else {
|
||||
tree[item.parentId].children.push(tree[item.id]); // 子节点添加到父节点的children数组
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
export const getComponentName = (route: RouteLocationNormalized) => {
|
||||
return route.matched[route.matched.length - 1]?.components?.default?.name;
|
||||
};
|
||||
|
||||
|
||||
export function streamFileDownload(file: any, fileName = "文件名称.zip") {
|
||||
const blob = new Blob([file], {type: "application/octet-stream;charset=UTF-8"});
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.style.display = "none";
|
||||
link.href = url;
|
||||
link.setAttribute("download", fileName);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link); // 下载完成移除元素
|
||||
window.URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
|
||||
export function filterName(list: any, value: any, key: string = "value", label: string = "label") {
|
||||
let name = "";
|
||||
let child = list.find(item => item[key] == value);
|
||||
name = child ? child[label] : "";
|
||||
return name;
|
||||
}
|
||||
|
||||
export function filterDatetime(value: any, format: string = "yyyy-MM-dd") {
|
||||
if (value) {
|
||||
return dateFtt(format, new Date(parseInt(value)));
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function dateFtt(fmt, date) { //author: meizz
|
||||
var o = {
|
||||
"M+": date.getMonth() + 1, //月份
|
||||
"d+": date.getDate(), //日
|
||||
"h+": date.getHours(), //小时
|
||||
"m+": date.getMinutes(), //分
|
||||
"s+": date.getSeconds(), //秒
|
||||
"q+": Math.floor((date.getMonth() + 3) / 3), //季度
|
||||
"S": date.getMilliseconds() //毫秒
|
||||
};
|
||||
if (/(y+)/.test(fmt))
|
||||
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
|
||||
for (var k in o)
|
||||
if (new RegExp("(" + k + ")").test(fmt))
|
||||
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
|
||||
return fmt;
|
||||
}
|
||||
|
||||
|
18
src/utils/useLockFn.ts
Normal file
18
src/utils/useLockFn.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
export function useLockFn(fn: (...args: any[]) => Promise<any>) {
|
||||
const isLock = ref(false)
|
||||
const lockFn = async (...args: any[]) => {
|
||||
if (isLock.value) return
|
||||
isLock.value = true
|
||||
try {
|
||||
await fn(...args)
|
||||
} finally{
|
||||
isLock.value=false
|
||||
}
|
||||
}
|
||||
return {
|
||||
isLock,
|
||||
lockFn
|
||||
}
|
||||
}
|
318
src/views/system/menu/edit.vue
Normal file
318
src/views/system/menu/edit.vue
Normal file
@ -0,0 +1,318 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="props.visible"
|
||||
:title="props.menuId?'编辑':'新增'"
|
||||
:append-to-body="true"
|
||||
width="600"
|
||||
:close-on-click-modal="false"
|
||||
:before-close="dialogClose"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="菜单类型" prop="type" required>
|
||||
<el-radio-group v-model="formData.type">
|
||||
<el-radio :label="0">菜单</el-radio>
|
||||
<el-radio :label="1">按钮</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="父级菜单"
|
||||
prop="pid"
|
||||
:rules="{ required: true, message: '请选择父级菜单', trigger: 'change' }"
|
||||
>
|
||||
<el-tree-select
|
||||
class="flex-1"
|
||||
v-model="formData.pid"
|
||||
:data="menuOptions"
|
||||
clearable
|
||||
node-key="id"
|
||||
:props="{
|
||||
label: 'name',
|
||||
}"
|
||||
:default-expand-all="true"
|
||||
placeholder="请选择父级菜单"
|
||||
check-strictly
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="菜单名称"
|
||||
prop="menuName"
|
||||
:rules="{ required: true, message: '请输入菜单名称', trigger: 'blur' }"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.menuName"
|
||||
placeholder="请输入菜单名称"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.type ==0"
|
||||
label="菜单图标"
|
||||
prop="icon"
|
||||
>
|
||||
<IconPicker class="flex-1" v-model="formData.icon"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
v-if="formData.type==0"
|
||||
label="路由路径"
|
||||
prop="paths"
|
||||
:rules="{ required: true, message: '请输入路由路径', trigger: 'blur' }"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<el-input
|
||||
v-model="formData.paths"
|
||||
placeholder="请输入路由路径"
|
||||
clearable
|
||||
/>
|
||||
<div class="form-tips">
|
||||
访问的路由地址,如:`admin`,如外网地址需内链访问则以`http(s)://`开头
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.type == 0"
|
||||
label="组件路径"
|
||||
prop="component"
|
||||
:rules="{ required: true, message: '请输入组件路径', trigger: 'blur' }"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<el-autocomplete
|
||||
style="width: 100%"
|
||||
v-model="formData.component"
|
||||
:fetch-suggestions="querySearch"
|
||||
clearable
|
||||
placeholder="请输入组件路径"
|
||||
/>
|
||||
<div class="form-tips">
|
||||
访问的组件路径,如:`permission/admin/index`,默认在`views`目录下
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="选中菜单"
|
||||
prop="selected"
|
||||
v-if="formData.type== 0"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<el-input
|
||||
v-model="formData.selected"
|
||||
placeholder="请输入路由路径"
|
||||
clearable
|
||||
/>
|
||||
<div class="form-tips">
|
||||
访问详情页面,编辑页面时,菜单高亮显示,如`/consumer/lists`
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="权限字符"
|
||||
prop="perms"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<el-input
|
||||
v-model="formData.perms"
|
||||
placeholder="请输入权限字符"
|
||||
clearable
|
||||
/>
|
||||
<div class="form-tips">
|
||||
将作为server端API验权使用,如`system:admin:list`,请谨慎修改
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.type == 0"
|
||||
label="路由参数"
|
||||
prop="params"
|
||||
>
|
||||
<div>
|
||||
<div class="flex-1">
|
||||
<el-input
|
||||
v-model="formData.params"
|
||||
placeholder="请输入路由参数"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
访问路由的默认传递参数,如:`{"menuId": 1, "name":
|
||||
"admin"}`或`menuId=1&name=admin`
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.type ==0"
|
||||
label="是否显示"
|
||||
prop="isShow"
|
||||
required
|
||||
>
|
||||
<div>
|
||||
<el-radio-group v-model="formData.isShow">
|
||||
<el-radio :label="1">显示</el-radio>
|
||||
<el-radio :label="0">隐藏</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="form-tips">
|
||||
选择隐藏则路由将不会出现在侧边栏,但仍然可以访问
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.type ==0"
|
||||
label="菜单状态"
|
||||
prop="isDisable"
|
||||
required
|
||||
>
|
||||
<div>
|
||||
<el-radio-group v-model="formData.isDisable">
|
||||
<el-radio :label="0">正常</el-radio>
|
||||
<el-radio :label="1">停用</el-radio>
|
||||
</el-radio-group>
|
||||
<div class="form-tips">
|
||||
选择停用则路由将不会出现在侧边栏,也不能被访问
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单排序" prop="menuSort">
|
||||
<div>
|
||||
<el-input-number v-model="formData.menuSort" :max="9999"/>
|
||||
<div class="form-tips">数值越小越排前</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogClose">取消</el-button>
|
||||
<el-button :loading="subLoading" type="primary" @click="submit">
|
||||
确定
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type {FormInstance} from "element-plus";
|
||||
import { menuAdd,menuUpdate,getMenuList } from '@/api/system/menu';
|
||||
import {onMounted, reactive, readonly, ref, shallowRef} from "vue";
|
||||
import {getModulesKey} from "@/router";
|
||||
import {arrayToTree, treeToArray,message,buildTree} from "@/utils/auth";
|
||||
import {useLockFn} from "@/utils/useLockFn";
|
||||
import IconPicker from "@/components/icon/picker.vue";
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false
|
||||
},
|
||||
menuId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
pid: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
});
|
||||
const emit = defineEmits(["success","update:visible"]);
|
||||
const formRef = shallowRef<FormInstance>();
|
||||
|
||||
const componentsOptions = ref(getModulesKey());
|
||||
const querySearch = (queryString: string, cb: any) => {
|
||||
const results = queryString
|
||||
? componentsOptions.value.filter((item) =>
|
||||
item.toLowerCase().includes(queryString.toLowerCase())
|
||||
)
|
||||
: componentsOptions.value;
|
||||
cb(results.map((item) => ({value: item})));
|
||||
};
|
||||
|
||||
const formData = reactive({
|
||||
menuId: "",
|
||||
//父级id
|
||||
pid: 0,
|
||||
//类型
|
||||
type: 0,
|
||||
//图标
|
||||
menuIcon: "",
|
||||
//名称
|
||||
menuName: "",
|
||||
//排序号
|
||||
menuSort: 0,
|
||||
// 路由路径
|
||||
paths: "",
|
||||
//权限链接
|
||||
perms: "",
|
||||
//前端组件
|
||||
component: "",
|
||||
//选中路径
|
||||
selected: "",
|
||||
//路由参数
|
||||
params: "",
|
||||
//是否缓存 0=否, 1=是
|
||||
isCache: 0,
|
||||
//是否显示 0=否, 1=是
|
||||
isShow: 1,
|
||||
//是否禁用 0=否, 1=是
|
||||
isDisable: 0
|
||||
});
|
||||
|
||||
const dialogClose = () => {
|
||||
emit("update:visible", false);
|
||||
};
|
||||
|
||||
const menuOptions = ref<any[]>([]);
|
||||
|
||||
const getMenu = async () => {
|
||||
const data: any = await getMenuList();
|
||||
const menu: any = {id: 0, name: "顶级", children: []};
|
||||
const lists = buildTree(data)
|
||||
console.log(lists)
|
||||
menu.children = arrayToTree(
|
||||
treeToArray(lists).filter((item) => item.type ==0)
|
||||
);
|
||||
menuOptions.value.push(menu);
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await formRef.value?.validate();
|
||||
props.menuId ? await menuUpdate(formData) : await menuAdd(formData);
|
||||
message("操作成功");
|
||||
emit("update:visible", false);
|
||||
emit("success");
|
||||
};
|
||||
|
||||
const { isLock:subLoading,lockFn: submit } = useLockFn(handleSubmit);
|
||||
|
||||
|
||||
const setFormData = (data: Record<any, any>) => {
|
||||
for (const key in formData) {
|
||||
if (data[key] != null && data[key] != undefined) {
|
||||
formData[key] = data[key];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getDetail = async () => {
|
||||
const data = await api.menuDetail(
|
||||
props.menuId
|
||||
);
|
||||
setFormData(data);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
emit("close");
|
||||
};
|
||||
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getMenu()
|
||||
if (props.menuId) {
|
||||
getDetail()
|
||||
}else{
|
||||
formData.pid=props.pid
|
||||
}
|
||||
});
|
||||
</script>
|
@ -1,258 +1,154 @@
|
||||
<template>
|
||||
<PageWrapper title="菜单权限管理">
|
||||
<el-row :gutter="10" class="mt-3">
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="6" :xl="6">
|
||||
<el-card shadow="hover" class="border-0" size="small">
|
||||
<template #header>
|
||||
<el-space>
|
||||
<el-dropdown trigger="hover" @command="selectAddMenu">
|
||||
<el-button type="primary" ghost icon-placement="right">
|
||||
添加菜单
|
||||
<div class="flex items-center">
|
||||
<el-icon class="el-input__icon" size="14">
|
||||
<DownOutlined />
|
||||
</el-icon>
|
||||
</div>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-for="item in addMenuOptions"
|
||||
:key="item.key"
|
||||
:disabled="item.disabled"
|
||||
:command="item.key"
|
||||
>{{ item.label }}</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button plain icon-placement="left" @click="packHandle">
|
||||
全部{{ expandAllStatus ? '收起' : '展开' }}
|
||||
<el-icon class="el-icon--right" v-if="expandAllStatus"><arrow-up /> </el-icon>
|
||||
<el-icon class="el-icon--right" v-else><arrow-down /></el-icon>
|
||||
</el-button>
|
||||
</el-space>
|
||||
<div class="menu-index">
|
||||
<el-card :bordered="false" class="pt-3 mb-3 proCard">
|
||||
<div>
|
||||
<el-button type="primary" @click="handleAdd()">
|
||||
<template #icon>
|
||||
<el-icon>
|
||||
<plus />
|
||||
</el-icon>
|
||||
</template>
|
||||
<div class="w-full menu">
|
||||
<el-input type="input" v-model="filterText" placeholder="输入菜单名称搜索">
|
||||
<template #suffix>
|
||||
<el-icon class="cursor-pointer el-input__icon" :size="18">
|
||||
<SearchOutlined />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="py-3 menu-list" v-loading="loading">
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
show-checkbox
|
||||
node-key="key"
|
||||
:data="treeData"
|
||||
:filter-node-method="filterNode"
|
||||
style="max-height: 650px; overflow: hidden"
|
||||
@current-change="currentChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="18" :xl="18">
|
||||
<el-card class="border-0" shadow="hover" size="small">
|
||||
<template #header>
|
||||
<el-space>
|
||||
<el-icon class="el-input__icon" size="18">
|
||||
<FormOutlined />
|
||||
</el-icon>
|
||||
<span>编辑菜单{{ treeItemTitle ? `:${treeItemTitle}` : '' }}</span>
|
||||
</el-space>
|
||||
新增
|
||||
</el-button>
|
||||
<el-button @click="handleExpand" v-perm="['sys:user:add']"> 展开/折叠</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card :bordered="false" class="pt-3 mb-3 proCard">
|
||||
<el-table border v-loading="loading" ref="tableRef" :data="lists" row-key="id"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }">
|
||||
<el-table-column label="菜单名称" prop="name" min-width="150" show-overflow-tooltip/>
|
||||
<el-table-column align="center" label="类型" prop="type" min-width="80">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.type ==0">菜单</div>
|
||||
<div v-else-if="row.type == 1">按钮</div>
|
||||
</template>
|
||||
<el-alert type="info" closable> 从菜单列表选择一项后,进行编辑</el-alert>
|
||||
<el-form
|
||||
:model="formParams"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="100"
|
||||
v-if="isEditMenu"
|
||||
class="py-4"
|
||||
>
|
||||
<el-form-item label="类型" prop="type">
|
||||
<span>{{ formParams.type === 1 ? '侧边栏菜单' : '' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="标题" prop="label">
|
||||
<el-input placeholder="请输入标题" v-model="formParams.label" />
|
||||
</el-form-item>
|
||||
<el-form-item label="副标题" prop="subtitle">
|
||||
<el-input placeholder="请输入副标题" v-model="formParams.subtitle" />
|
||||
</el-form-item>
|
||||
<el-form-item label="路径" prop="path">
|
||||
<el-input placeholder="请输入路径" v-model="formParams.path" />
|
||||
</el-form-item>
|
||||
<el-form-item label="打开方式" prop="openType">
|
||||
<el-radio-group v-model="formParams.openType" name="openType">
|
||||
<el-radio :label="1">当前窗口</el-radio>
|
||||
<el-radio :label="2">新窗口</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单权限" prop="auth">
|
||||
<el-input placeholder="请输入权限,多个权限用,分割" v-model="formParams.auth" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-space>
|
||||
<el-button type="primary" :loading="subLoading" @click="formSubmit(formRef)"
|
||||
>保存修改</el-button
|
||||
>
|
||||
<el-button @click="resetForm(formRef)">重置</el-button>
|
||||
</el-space>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<CreateDrawer ref="createDrawerRef" :title="drawerTitle" />
|
||||
</PageWrapper>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="图标" prop="icon" min-width="80">
|
||||
<template #default="{ row }">
|
||||
<icon :name="row.icon" :size="20"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="权限标识" prop="permission" min-width="150" show-overflow-tooltip/>
|
||||
<el-table-column align="center" label="状态" prop="status" min-width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.status == 0">正常</el-tag>
|
||||
<el-tag v-else type="danger">停用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" label="排序" prop="sort" min-width="100"/>
|
||||
<el-table-column align="center" label="更新时间" prop="updateTime" min-width="180"></el-table-column>
|
||||
<el-table-column align="center" label="操作" width="160" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button v-if="row.type !== 1" type="primary" link
|
||||
@click="handleAdd(row.id)">
|
||||
新增
|
||||
</el-button>
|
||||
<el-button type="primary" link @click="handleEdit(row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button type="danger" link @click="handleDelete(row.id)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
<editDialog
|
||||
ref="editRef"
|
||||
v-if="editVisible"
|
||||
:menuId="menuId"
|
||||
:pid="pid"
|
||||
v-model:visible="editVisible"
|
||||
@success="getLists"
|
||||
>
|
||||
</editDialog>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, unref, reactive, onMounted, computed } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { DownOutlined, SearchOutlined, FormOutlined } from '@vicons/antd';
|
||||
import { getMenuList } from '@/api/system/menu';
|
||||
import { getTreeItem } from '@/utils';
|
||||
import CreateDrawer from './CreateDrawer.vue';
|
||||
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
|
||||
import type { ElForm } from 'element-plus';
|
||||
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
<script lang="ts" setup name="menu">
|
||||
import {defineAsyncComponent, nextTick, onMounted, readonly, ref, shallowRef,onActivated} from "vue";
|
||||
import {getMenuList,menuDelete} from "@/api/system/menu";
|
||||
import type {ElTable} from "element-plus";
|
||||
|
||||
interface Tree {
|
||||
id: number;
|
||||
label: string;
|
||||
children?: Tree[];
|
||||
}
|
||||
const tableRef = shallowRef<InstanceType<typeof ElTable>>();
|
||||
import {confirm, message,buildTree} from "@/utils/auth";
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
const editDialog = defineAsyncComponent(() =>
|
||||
import('./edit.vue')
|
||||
)
|
||||
|
||||
const rules = {
|
||||
label: {
|
||||
required: true,
|
||||
message: '请输入标题',
|
||||
trigger: 'blur',
|
||||
},
|
||||
path: {
|
||||
required: true,
|
||||
message: '请输入路径',
|
||||
trigger: 'blur',
|
||||
},
|
||||
};
|
||||
const isExpand = ref(false);
|
||||
const loading = ref(false);
|
||||
const editVisible=ref(false);
|
||||
const menuId=ref(0);
|
||||
const pid=ref(0)
|
||||
const lists = ref([]);
|
||||
|
||||
const createDrawerRef = ref();
|
||||
|
||||
let treeItemKey = ref([]);
|
||||
|
||||
const treeData = ref([]);
|
||||
const treeRef = ref();
|
||||
|
||||
const loading = ref(true);
|
||||
const subLoading = ref(false);
|
||||
const isEditMenu = ref(false);
|
||||
const expandAllStatus = ref(false);
|
||||
const treeItemTitle = ref('');
|
||||
const drawerTitle = ref('');
|
||||
const filterText = ref('');
|
||||
|
||||
watch(filterText, (val) => {
|
||||
treeRef.value!.filter(val);
|
||||
});
|
||||
|
||||
const filterNode = (value: string, data: Tree) => {
|
||||
if (!value) return true;
|
||||
return data.label.includes(value);
|
||||
};
|
||||
|
||||
const isAddSon = computed(() => {
|
||||
return !treeItemKey.value.length;
|
||||
});
|
||||
|
||||
const addMenuOptions = ref([
|
||||
{
|
||||
label: '添加顶级菜单',
|
||||
key: 'home',
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: '添加子菜单',
|
||||
key: 'son',
|
||||
disabled: isAddSon,
|
||||
},
|
||||
]);
|
||||
|
||||
const formParams = reactive({
|
||||
type: 1,
|
||||
label: '',
|
||||
subtitle: '',
|
||||
path: '',
|
||||
auth: '',
|
||||
openType: 1,
|
||||
});
|
||||
|
||||
function selectAddMenu(key: string) {
|
||||
drawerTitle.value = key === 'home' ? '添加顶栏菜单' : `添加子菜单:${treeItemTitle.value}`;
|
||||
openCreateDrawer();
|
||||
}
|
||||
|
||||
function openCreateDrawer() {
|
||||
const { openDrawer } = createDrawerRef.value;
|
||||
openDrawer();
|
||||
}
|
||||
|
||||
//当前选中节点变化时触发的事件
|
||||
function currentChange(res) {
|
||||
const key = res.key;
|
||||
if (key) {
|
||||
const treeItem = getTreeItem(unref(treeData), key);
|
||||
treeItemKey.value = key;
|
||||
treeItemTitle.value = treeItem.label;
|
||||
Object.assign(formParams, treeItem);
|
||||
isEditMenu.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
function formSubmit(formEl: FormInstance | undefined) {
|
||||
if (!formEl) return;
|
||||
formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
ElMessage.success('抱歉,您没有该权限');
|
||||
} else {
|
||||
ElMessage.error('验证失败,请填写完整信息');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resetForm(formEl: FormInstance | undefined) {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
}
|
||||
|
||||
function packHandle() {
|
||||
if (expandAllStatus.value) {
|
||||
treeNodeExpand(false);
|
||||
expandAllStatus.value = false;
|
||||
} else {
|
||||
treeNodeExpand(true);
|
||||
expandAllStatus.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
function treeNodeExpand(status) {
|
||||
for (var i = 0; i < treeRef.value.store._getAllNodes().length; i++) {
|
||||
treeRef.value.store._getAllNodes()[i].expanded = status;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const treeMenuList = await getMenuList();
|
||||
const keys = treeMenuList.map((item) => item.key);
|
||||
Object.assign(formParams, keys);
|
||||
treeData.value = treeMenuList;
|
||||
const getLists = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getMenuList();
|
||||
lists.value = buildTree(data);
|
||||
loading.value = false;
|
||||
});
|
||||
} catch (error) {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleAdd = async (parentId:any) => {
|
||||
menuId.value=0
|
||||
pid.value=parentId?parentId:0
|
||||
await nextTick();
|
||||
editVisible.value=true
|
||||
};
|
||||
|
||||
const handleEdit = async (data: any) => {
|
||||
menuId.value=data.menuId
|
||||
await nextTick();
|
||||
editVisible.value=true
|
||||
};
|
||||
|
||||
const handleDelete = async (menuId: number) => {
|
||||
await confirm("确定要删除?");
|
||||
try {
|
||||
loading.value = true;
|
||||
await menuDelete(menuId);
|
||||
message("删除成功");
|
||||
getLists();
|
||||
} catch (e) {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleExpand = () => {
|
||||
isExpand.value = !isExpand.value;
|
||||
toggleExpand(lists.value, isExpand.value);
|
||||
};
|
||||
|
||||
const toggleExpand = (children: any[], unfold = true) => {
|
||||
for (const key in children) {
|
||||
tableRef.value?.toggleRowExpansion(children[key], unfold);
|
||||
if (children[key].children) {
|
||||
toggleExpand(children[key].children!, unfold);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getAll=()=>{
|
||||
getLists();
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
getAll()
|
||||
});
|
||||
|
||||
// onActivated(() => {
|
||||
// getAll()
|
||||
// });
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
258
src/views/system/menu/index2.vue
Normal file
258
src/views/system/menu/index2.vue
Normal file
@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<PageWrapper title="菜单权限管理">
|
||||
<el-row :gutter="10" class="mt-3">
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="6" :xl="6">
|
||||
<el-card shadow="hover" class="border-0" size="small">
|
||||
<template #header>
|
||||
<el-space>
|
||||
<el-dropdown trigger="hover" @command="selectAddMenu">
|
||||
<el-button type="primary" ghost icon-placement="right">
|
||||
添加菜单
|
||||
<div class="flex items-center">
|
||||
<el-icon class="el-input__icon" size="14">
|
||||
<DownOutlined />
|
||||
</el-icon>
|
||||
</div>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
v-for="item in addMenuOptions"
|
||||
:key="item.key"
|
||||
:disabled="item.disabled"
|
||||
:command="item.key"
|
||||
>{{ item.label }}</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button plain icon-placement="left" @click="packHandle">
|
||||
全部{{ expandAllStatus ? '收起' : '展开' }}
|
||||
<el-icon class="el-icon--right" v-if="expandAllStatus"><arrow-up /> </el-icon>
|
||||
<el-icon class="el-icon--right" v-else><arrow-down /></el-icon>
|
||||
</el-button>
|
||||
</el-space>
|
||||
</template>
|
||||
<div class="w-full menu">
|
||||
<el-input type="input" v-model="filterText" placeholder="输入菜单名称搜索">
|
||||
<template #suffix>
|
||||
<el-icon class="cursor-pointer el-input__icon" :size="18">
|
||||
<SearchOutlined />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="py-3 menu-list" v-loading="loading">
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
show-checkbox
|
||||
node-key="key"
|
||||
:data="treeData"
|
||||
:filter-node-method="filterNode"
|
||||
style="max-height: 650px; overflow: hidden"
|
||||
@current-change="currentChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :md="12" :lg="18" :xl="18">
|
||||
<el-card class="border-0" shadow="hover" size="small">
|
||||
<template #header>
|
||||
<el-space>
|
||||
<el-icon class="el-input__icon" size="18">
|
||||
<FormOutlined />
|
||||
</el-icon>
|
||||
<span>编辑菜单{{ treeItemTitle ? `:${treeItemTitle}` : '' }}</span>
|
||||
</el-space>
|
||||
</template>
|
||||
<el-alert type="info" closable> 从菜单列表选择一项后,进行编辑</el-alert>
|
||||
<el-form
|
||||
:model="formParams"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="100"
|
||||
v-if="isEditMenu"
|
||||
class="py-4"
|
||||
>
|
||||
<el-form-item label="类型" prop="type">
|
||||
<span>{{ formParams.type === 1 ? '侧边栏菜单' : '' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="标题" prop="label">
|
||||
<el-input placeholder="请输入标题" v-model="formParams.label" />
|
||||
</el-form-item>
|
||||
<el-form-item label="副标题" prop="subtitle">
|
||||
<el-input placeholder="请输入副标题" v-model="formParams.subtitle" />
|
||||
</el-form-item>
|
||||
<el-form-item label="路径" prop="path">
|
||||
<el-input placeholder="请输入路径" v-model="formParams.path" />
|
||||
</el-form-item>
|
||||
<el-form-item label="打开方式" prop="openType">
|
||||
<el-radio-group v-model="formParams.openType" name="openType">
|
||||
<el-radio :label="1">当前窗口</el-radio>
|
||||
<el-radio :label="2">新窗口</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="菜单权限" prop="auth">
|
||||
<el-input placeholder="请输入权限,多个权限用,分割" v-model="formParams.auth" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-space>
|
||||
<el-button type="primary" :loading="subLoading" @click="formSubmit(formRef)"
|
||||
>保存修改</el-button
|
||||
>
|
||||
<el-button @click="resetForm(formRef)">重置</el-button>
|
||||
</el-space>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<CreateDrawer ref="createDrawerRef" :title="drawerTitle" />
|
||||
</PageWrapper>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, unref, reactive, onMounted, computed } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { DownOutlined, SearchOutlined, FormOutlined } from '@vicons/antd';
|
||||
import { getMenuList } from '@/api/system/menu';
|
||||
import { getTreeItem } from '@/utils';
|
||||
import CreateDrawer from './CreateDrawer.vue';
|
||||
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
|
||||
import type { ElForm } from 'element-plus';
|
||||
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
|
||||
interface Tree {
|
||||
id: number;
|
||||
label: string;
|
||||
children?: Tree[];
|
||||
}
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const rules = {
|
||||
label: {
|
||||
required: true,
|
||||
message: '请输入标题',
|
||||
trigger: 'blur',
|
||||
},
|
||||
path: {
|
||||
required: true,
|
||||
message: '请输入路径',
|
||||
trigger: 'blur',
|
||||
},
|
||||
};
|
||||
|
||||
const createDrawerRef = ref();
|
||||
|
||||
let treeItemKey = ref([]);
|
||||
|
||||
const treeData = ref([]);
|
||||
const treeRef = ref();
|
||||
|
||||
const loading = ref(true);
|
||||
const subLoading = ref(false);
|
||||
const isEditMenu = ref(false);
|
||||
const expandAllStatus = ref(false);
|
||||
const treeItemTitle = ref('');
|
||||
const drawerTitle = ref('');
|
||||
const filterText = ref('');
|
||||
|
||||
watch(filterText, (val) => {
|
||||
treeRef.value!.filter(val);
|
||||
});
|
||||
|
||||
const filterNode = (value: string, data: Tree) => {
|
||||
if (!value) return true;
|
||||
return data.label.includes(value);
|
||||
};
|
||||
|
||||
const isAddSon = computed(() => {
|
||||
return !treeItemKey.value.length;
|
||||
});
|
||||
|
||||
const addMenuOptions = ref([
|
||||
{
|
||||
label: '添加顶级菜单',
|
||||
key: 'home',
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: '添加子菜单',
|
||||
key: 'son',
|
||||
disabled: isAddSon,
|
||||
},
|
||||
]);
|
||||
|
||||
const formParams = reactive({
|
||||
type: 1,
|
||||
label: '',
|
||||
subtitle: '',
|
||||
path: '',
|
||||
auth: '',
|
||||
openType: 1,
|
||||
});
|
||||
|
||||
function selectAddMenu(key: string) {
|
||||
drawerTitle.value = key === 'home' ? '添加顶栏菜单' : `添加子菜单:${treeItemTitle.value}`;
|
||||
openCreateDrawer();
|
||||
}
|
||||
|
||||
function openCreateDrawer() {
|
||||
const { openDrawer } = createDrawerRef.value;
|
||||
openDrawer();
|
||||
}
|
||||
|
||||
//当前选中节点变化时触发的事件
|
||||
function currentChange(res) {
|
||||
const key = res.key;
|
||||
if (key) {
|
||||
const treeItem = getTreeItem(unref(treeData), key);
|
||||
treeItemKey.value = key;
|
||||
treeItemTitle.value = treeItem.label;
|
||||
Object.assign(formParams, treeItem);
|
||||
isEditMenu.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
function formSubmit(formEl: FormInstance | undefined) {
|
||||
if (!formEl) return;
|
||||
formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
ElMessage.success('抱歉,您没有该权限');
|
||||
} else {
|
||||
ElMessage.error('验证失败,请填写完整信息');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resetForm(formEl: FormInstance | undefined) {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
}
|
||||
|
||||
function packHandle() {
|
||||
if (expandAllStatus.value) {
|
||||
treeNodeExpand(false);
|
||||
expandAllStatus.value = false;
|
||||
} else {
|
||||
treeNodeExpand(true);
|
||||
expandAllStatus.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
function treeNodeExpand(status) {
|
||||
for (var i = 0; i < treeRef.value.store._getAllNodes().length; i++) {
|
||||
treeRef.value.store._getAllNodes()[i].expanded = status;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const treeMenuList = await getMenuList();
|
||||
const keys = treeMenuList.map((item) => item.key);
|
||||
Object.assign(formParams, keys);
|
||||
treeData.value = treeMenuList;
|
||||
loading.value = false;
|
||||
});
|
||||
</script>
|
@ -1,77 +0,0 @@
|
||||
<template>
|
||||
<basicModal @register="modalRegister" ref="modalRef" @ok="okModal">
|
||||
<template #default>
|
||||
<BasicForm @register="registerForm">
|
||||
<template #passwordSlot="{ model, field }">
|
||||
<Password ref="passwordRef" v-model="model[field]" :required="false" block />
|
||||
</template>
|
||||
<template #rePasswordSlot="{ model, field }">
|
||||
<Password ref="passwordRef" v-model="model[field]" :required="false" block />
|
||||
</template>
|
||||
</BasicForm>
|
||||
</template>
|
||||
</basicModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { basicModal, useModal } from '@/components/Modal';
|
||||
import { Password } from '@/components/Password';
|
||||
import { schemas } from './modalSchemas';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '添加用户',
|
||||
},
|
||||
isEdit: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const title = ref(props.title);
|
||||
|
||||
//创建form
|
||||
const [registerForm, { submit, setFieldsValue, getFieldsValue }] = useForm({
|
||||
layout: 'horizontal',
|
||||
colProps: { span: 12 },
|
||||
labelWidth: 80,
|
||||
submitButtonText: '确定',
|
||||
showActionButtonGroup: false,
|
||||
schemas,
|
||||
});
|
||||
|
||||
//添加会员弹窗
|
||||
const [modalRegister, { openModal, closeModal, setSubLoading, setProps }] = useModal({
|
||||
title,
|
||||
confirmButText: '保存',
|
||||
width: 650,
|
||||
});
|
||||
|
||||
//提交
|
||||
async function okModal() {
|
||||
const formRes = await submit();
|
||||
if (formRes) {
|
||||
// const { isEdit } = props;
|
||||
console.log('表单值:', getFieldsValue());
|
||||
ElMessage.error('抱歉,您没有操作权限');
|
||||
//ElMessage.success(isEdit ? '修改成功' : '添加成功');
|
||||
setSubLoading(false);
|
||||
closeModal();
|
||||
} else {
|
||||
ElMessage.error('验证失败,请填写完整信息');
|
||||
setSubLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
//导出方法
|
||||
defineExpose({
|
||||
openModal,
|
||||
closeModal,
|
||||
setFieldsValue,
|
||||
setProps,
|
||||
});
|
||||
</script>
|
206
src/views/system/user/edit.vue
Normal file
206
src/views/system/user/edit.vue
Normal file
@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="props.visible"
|
||||
:title="props.userId?'编辑':'新增'"
|
||||
width="500"
|
||||
:close-on-click-modal="false"
|
||||
:before-close="dialogClose"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
label-width="84px"
|
||||
>
|
||||
<el-form-item
|
||||
label="账号"
|
||||
prop="username"
|
||||
:rules="{ required: true, message: '请输入账号', trigger: 'blur' }"
|
||||
>
|
||||
<el-input
|
||||
:disabled="isRoot"
|
||||
v-model="formData.username"
|
||||
placeholder="请输入账号"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="名称"
|
||||
prop="nickname"
|
||||
:rules="{ required: true, message: '请输入名称', trigger: 'blur' }"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.nickname"
|
||||
placeholder="请输入名称"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="邮箱地址"
|
||||
prop="email"
|
||||
:rules="{ required: true, message: '请输入邮箱地址', trigger: 'blur' }"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.email"
|
||||
placeholder="请输入邮箱地址"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="角色"
|
||||
prop="roleIds"
|
||||
:rules="{ required: true, message: '请选择角色', trigger: 'change' }"
|
||||
>
|
||||
<el-select
|
||||
v-model="formData.roleIds"
|
||||
:disabled="isRoot"
|
||||
multiple
|
||||
class="flex-1"
|
||||
clearable
|
||||
placeholder="请选择角色"
|
||||
>
|
||||
<el-option v-if="isRoot" label="系统管理员" value="0" />
|
||||
<el-option
|
||||
v-for="(item, index) in optionData.role"
|
||||
:key="index"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="密码"
|
||||
prop="password"
|
||||
:rules="{ required: props.userId?false:true, message: '请输入密码', trigger: 'blur' }"
|
||||
>
|
||||
<el-input
|
||||
v-model.trim="formData.password"
|
||||
show-password
|
||||
autocomplete="new-password"
|
||||
clearable
|
||||
placeholder="请输入密码"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
label="确认密码"
|
||||
prop="passwordConfirm"
|
||||
:rules="[{ required: props.userId?false:true, message: '请输入密码', trigger: 'blur' },{validator: props.userId?void(0):passwordConfirmValidator,trigger: 'blur'}]"
|
||||
>
|
||||
<el-input
|
||||
v-model.trim="formData.passwordConfirm"
|
||||
autocomplete="new-password"
|
||||
show-password
|
||||
clearable
|
||||
placeholder="请输入确认密码"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="管理员状态" v-if="!isRoot">
|
||||
<el-switch
|
||||
v-model="formData.userStatus"
|
||||
active-value="enable"
|
||||
inactive-value="disable"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogClose">取消</el-button>
|
||||
<el-button :loading="subLoading" type="primary" @click="submit">
|
||||
确定
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {getUserDetail,userAdd,userUpdate} from '@/api/system/user';
|
||||
import {computed, onMounted, reactive, shallowRef} from "vue";
|
||||
import {message} from "@/utils/auth";
|
||||
import {FormInstance} from "element-plus";
|
||||
const formRef = shallowRef<FormInstance>();
|
||||
import {useLockFn} from "@/utils/useLockFn";
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false
|
||||
},
|
||||
userId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
roleList: {
|
||||
type: Array,
|
||||
default: []
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(["success", "update:visible"]);
|
||||
|
||||
const formData = reactive({
|
||||
userId: 0,
|
||||
email:'',
|
||||
username: "",
|
||||
nickname: "",
|
||||
roleIds: [],
|
||||
avatar: "",
|
||||
password: "",
|
||||
passwordConfirm: "",
|
||||
userStatus: 'enable'
|
||||
});
|
||||
|
||||
const passwordConfirmValidator = (
|
||||
rule: object,
|
||||
value: string,
|
||||
callback: any
|
||||
) => {
|
||||
if (formData.password) {
|
||||
if (!value) callback(new Error("请再次输入密码"));
|
||||
if (value !== formData.password) callback(new Error("两次输入密码不一致!"));
|
||||
}
|
||||
callback();
|
||||
};
|
||||
|
||||
const isRoot = computed(() => {
|
||||
return formData.userId == 1;
|
||||
});
|
||||
|
||||
const dialogClose = () => {
|
||||
emit("update:visible", false);
|
||||
};
|
||||
|
||||
const setFormData = async (row: any) => {
|
||||
const data = await getUserDetail(row.userId);
|
||||
for (const key in formData) {
|
||||
if (data[key] != null && data[key] != undefined) {
|
||||
formData[key] = data[key];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await formRef.value?.validate();
|
||||
props.userId ? await userUpdate(formData) : await userAdd(formData);
|
||||
emit("update:visible", false);
|
||||
emit("success");
|
||||
};
|
||||
|
||||
const { isLock:subLoading,lockFn: submit } = useLockFn(handleSubmit);
|
||||
|
||||
const optionData = reactive({
|
||||
role:props.roleList
|
||||
});
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
if (props.userId) {
|
||||
setFormData({userId: props.userId});
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
@ -1,7 +1,11 @@
|
||||
<template>
|
||||
<PageWrapper>
|
||||
<el-card :bordered="false" class="pt-3 mb-3 proCard">
|
||||
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset"></BasicForm>
|
||||
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset">
|
||||
<template #statusSlot="{ model, field }">
|
||||
<el-input v-model="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
</el-card>
|
||||
<el-card :bordered="false" class="proCard">
|
||||
<BasicTable
|
||||
@ -10,7 +14,7 @@
|
||||
:row-key="(row) => row.id"
|
||||
ref="basicTableRef"
|
||||
:actionColumn="actionColumn"
|
||||
@checked-row-change="onCheckedRow"
|
||||
@selection-change="onSelectionChange"
|
||||
scroll-x="1200"
|
||||
virtual-scroll
|
||||
>
|
||||
@ -18,67 +22,55 @@
|
||||
<el-space>
|
||||
<el-button type="primary" @click="addUser">
|
||||
<template #icon>
|
||||
<el-icon class="el-input__icon">
|
||||
<PlusOutlined />
|
||||
<el-icon style="vertical-align: middle">
|
||||
<plus />
|
||||
</el-icon>
|
||||
</template>
|
||||
新建
|
||||
</el-button>
|
||||
|
||||
<el-button type="danger" @click="openRemoveModal" :disabled="!rowKeys.length">
|
||||
<el-button type="danger" @click="handleDelete" :disabled="!selectionData.length">
|
||||
<template #icon>
|
||||
<el-icon class="el-input__icon">
|
||||
<DeleteOutlined />
|
||||
<Delete />
|
||||
</el-icon>
|
||||
</template>
|
||||
删除
|
||||
</el-button>
|
||||
<el-button @click="addTable">
|
||||
<template #icon>
|
||||
<el-icon class="el-input__icon">
|
||||
<ToTopOutlined />
|
||||
</el-icon>
|
||||
</template>
|
||||
导入
|
||||
</el-button>
|
||||
</el-space>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</el-card>
|
||||
|
||||
<basicModal @register="lightModalRegister" ref="modalRef" @ok="removeOkModal">
|
||||
<template #default>
|
||||
<p class="text-gray-600" style="padding-left: 35px"
|
||||
>您确认要删除用户,<span strong>{{ rowKeysName }} ?</span></p
|
||||
>
|
||||
</template>
|
||||
</basicModal>
|
||||
|
||||
<CreateModal ref="createModalRef" :title="createModalTitle" :isEdit="isEdit" />
|
||||
<editDialog
|
||||
v-if="editVisible"
|
||||
:userId="userId"
|
||||
:roleList="roleList"
|
||||
v-model:visible="editVisible"
|
||||
@success="reloadTable"
|
||||
>
|
||||
</editDialog>
|
||||
</PageWrapper>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, nextTick, reactive, ref, unref } from 'vue';
|
||||
import { h, nextTick, reactive, ref, unref ,defineAsyncComponent,onMounted} from 'vue';
|
||||
import { ColProps, ElMessage } from 'element-plus';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { getUserList } from '@/api/system/user';
|
||||
import { getUserList,userDelete,userBatchDelete } from '@/api/system/user';
|
||||
import {message,confirm} from "@/utils/auth";
|
||||
import { getRoleAllList } from '@/api/system/role';
|
||||
import { columns } from './columns';
|
||||
import { PlusOutlined, DeleteOutlined, ToTopOutlined } from '@vicons/antd';
|
||||
import CreateModal from './CreateModal.vue';
|
||||
import { basicModal, useModal } from '@/components/Modal';
|
||||
import { schemas } from './querySchemas';
|
||||
|
||||
const userId = ref(0);
|
||||
const basicTableRef = ref();
|
||||
const createModalRef = ref();
|
||||
const rowKeys = ref([]);
|
||||
const rowKeysName = ref([]);
|
||||
const editVisible = ref(false)
|
||||
const roleList = ref([])
|
||||
const selectionData = ref([])
|
||||
const tableData = ref();
|
||||
const isEdit = ref(false);
|
||||
const createModalTitle = ref('添加用户');
|
||||
|
||||
const showModal = ref(false);
|
||||
const editDialog = defineAsyncComponent(() =>
|
||||
import('./edit.vue')
|
||||
)
|
||||
const formParams = reactive({
|
||||
username: '',
|
||||
mobile: '',
|
||||
@ -130,47 +122,27 @@
|
||||
},
|
||||
});
|
||||
|
||||
function addTable() {
|
||||
showModal.value = true;
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
const result = await getUserList({ ...formParams, ...params.value, ...res });
|
||||
tableData.value = result.records;
|
||||
return result;
|
||||
};
|
||||
|
||||
function onCheckedRow(keys) {
|
||||
rowKeys.value = keys;
|
||||
rowKeysName.value = tableData.value
|
||||
.filter((item) => {
|
||||
return keys.includes(item.id);
|
||||
})
|
||||
.map((item) => {
|
||||
return item.username;
|
||||
})
|
||||
.join(',');
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
basicTableRef.value.reload();
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
record.mobile = parseInt(record.mobile);
|
||||
addUser();
|
||||
nextTick(() => {
|
||||
isEdit.value = true;
|
||||
createModalRef.value.setProps({ title: '编辑用户' });
|
||||
createModalRef.value.setFieldsValue({
|
||||
...unref(record),
|
||||
});
|
||||
});
|
||||
async function handleEdit(record: Recordable) {
|
||||
userId.value=record.row.id
|
||||
await nextTick();
|
||||
editVisible.value=true
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
rowKeysName.value = record.row.username;
|
||||
openRemoveModal();
|
||||
async function handleDelete(record: Recordable) {
|
||||
await confirm('确定要删除?');
|
||||
selectionData.value.length>0? await userBatchDelete(selectionData.value.join()):await userDelete(record.row.id);
|
||||
message("删除成功");
|
||||
reloadTable()
|
||||
}
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
@ -187,7 +159,9 @@
|
||||
formParams[key] ='';
|
||||
}
|
||||
}
|
||||
|
||||
function onSelectionChange(value){
|
||||
selectionData.value = value
|
||||
}
|
||||
const [register, {}] = useForm({
|
||||
labelWidth: 80,
|
||||
layout: 'horizontal',
|
||||
@ -196,17 +170,6 @@
|
||||
schemas
|
||||
});
|
||||
|
||||
const [
|
||||
lightModalRegister,
|
||||
{ openModal: lightOpenModal, closeModal: lightCloseModal, setSubLoading: lightSetSubLoading },
|
||||
] = useModal({
|
||||
title: '删除确认',
|
||||
showIcon: true,
|
||||
type: 'warning',
|
||||
closable: false,
|
||||
maskClosable: true,
|
||||
width: 380,
|
||||
});
|
||||
|
||||
//删除
|
||||
function openRemoveModal() {
|
||||
@ -215,17 +178,23 @@
|
||||
|
||||
//添加
|
||||
function addUser() {
|
||||
isEdit.value = false;
|
||||
createModalRef.value.setProps({ title: '添加用户' });
|
||||
createModalRef.value.openModal();
|
||||
userId.value =0
|
||||
editVisible.value = true
|
||||
}
|
||||
|
||||
//确认删除
|
||||
function removeOkModal() {
|
||||
lightSetSubLoading(true);
|
||||
lightCloseModal();
|
||||
lightSetSubLoading();
|
||||
ElMessage.error('抱歉,您没有操作权限');
|
||||
reloadTable()
|
||||
}
|
||||
const getAllDict = async () => {
|
||||
const list = await getRoleAllList();
|
||||
roleList.value = list ? list : [];
|
||||
};
|
||||
onMounted(() => {
|
||||
getAllDict()
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
@ -1,122 +0,0 @@
|
||||
import { FormSchema } from '@/components/Form/index';
|
||||
import { getRoleAllList } from '@/api/system/role';
|
||||
export const loadSelectData = async(res)=> {
|
||||
//这里可以进行数据转换处理
|
||||
return (await getRoleAllList({ ...res })).map((item, index) => {
|
||||
return {
|
||||
...item,
|
||||
label:item.name,
|
||||
value:item.id,
|
||||
index,
|
||||
};
|
||||
});
|
||||
}
|
||||
export const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'username',
|
||||
component: 'Input',
|
||||
label: '用户名',
|
||||
componentProps: {
|
||||
placeholder: '请输入用户名',
|
||||
},
|
||||
rules: [{ required: true, message: '请输入用户名', trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'mobile',
|
||||
component: 'Input',
|
||||
label: '手机号',
|
||||
componentProps: {
|
||||
placeholder: '请输入手机号码',
|
||||
controls: false,
|
||||
},
|
||||
rules: [{ required: true, message: '请输入手机号码', trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'email',
|
||||
component: 'Input',
|
||||
label: '邮箱',
|
||||
componentProps: {
|
||||
placeholder: '请输入邮箱',
|
||||
},
|
||||
rules: [{ required: true, type: 'email', message: '请输入邮箱', trigger: ['change'] }],
|
||||
},
|
||||
{
|
||||
field: 'gender',
|
||||
component: 'Select',
|
||||
label: '性别',
|
||||
componentProps: {
|
||||
placeholder: '请选择性别',
|
||||
options: [
|
||||
{
|
||||
label: '男',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '女',
|
||||
value: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
field: 'status',
|
||||
component: 'Select',
|
||||
label: '状态',
|
||||
componentProps: {
|
||||
placeholder: '请选择角色',
|
||||
options: [
|
||||
{
|
||||
label: '正常',
|
||||
value: "1",
|
||||
},
|
||||
{
|
||||
label: '禁用',
|
||||
value: "2",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'role',
|
||||
component: 'BasicSelect',
|
||||
label: '角色',
|
||||
componentProps: {
|
||||
placeholder: '请选择角色',
|
||||
block:true,
|
||||
request: loadSelectData,
|
||||
onChange: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'account',
|
||||
component: 'Input',
|
||||
label: '登录账号',
|
||||
componentProps: {
|
||||
placeholder: '请输入登录账号',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'password',
|
||||
label: '密码',
|
||||
slot: 'passwordSlot',
|
||||
showFeedback: false,
|
||||
},
|
||||
{
|
||||
field: 'rePassword',
|
||||
label: '确认密码',
|
||||
slot: 'rePasswordSlot',
|
||||
showFeedback: false,
|
||||
},
|
||||
{
|
||||
field: 'introduce',
|
||||
component: 'Input',
|
||||
label: '个人介绍',
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
placeholder: '请输入登录账号',
|
||||
},
|
||||
},
|
||||
];
|
Loading…
Reference in New Issue
Block a user