This commit is contained in:
陈红丽 2024-07-08 19:24:07 +08:00
parent 7423baea13
commit b2b8ddf114
20 changed files with 1615 additions and 547 deletions

View File

@ -21,3 +21,42 @@ export function getMenuList(params?) {
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',
});
}

View File

@ -87,3 +87,50 @@ export function getUserList(params) {
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',
});
}

View 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
}

View 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>

View 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>

View 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>

View File

@ -1,19 +1,23 @@
import { ObjectDirective } from 'vue'; import { ObjectDirective } from 'vue';
import { usePermission } from '@/hooks/web/usePermission'; import { usePermission } from '@/hooks/web/usePermission';
export const permission: ObjectDirective = { export const perm: ObjectDirective = {
mounted(el: HTMLButtonElement, binding) { mounted(el: HTMLButtonElement, binding) {
if (binding.value == undefined) return; if (binding.value == undefined) return;
const { action, effect } = binding.value; const { action, effect } = binding.value;
const { hasPermission } = usePermission(); const { hasSomePermission } = usePermission();
if (!hasPermission(action)) { if (!hasSomePermission(binding.value)) {
if (effect == 'disabled') { el.parentNode.removeChild(el)
el.disabled = true; }
el.style['disabled'] = 'disabled'; },
el.classList.add('is-disabled'); };
} else { export const perms: ObjectDirective = {
el.remove(); 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)
} }
}, },
}; };

View File

@ -30,7 +30,7 @@ export function usePermission() {
function hasEveryPermission(accesses: string[]): boolean { function hasEveryPermission(accesses: string[]): boolean {
const permissionsList = userStore.getPermissions; const permissionsList = userStore.getPermissions;
if (Array.isArray(accesses)) { 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 !`); throw new Error(`[hasEveryPermission]: ${accesses} should be a array !`);
} }
@ -43,7 +43,7 @@ export function usePermission() {
function hasSomePermission(accesses: string[]): boolean { function hasSomePermission(accesses: string[]): boolean {
const permissionsList = userStore.getPermissions; const permissionsList = userStore.getPermissions;
if (Array.isArray(accesses)) { 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 !`); throw new Error(`[hasSomePermission]: ${accesses} should be a array !`);
} }

View File

@ -4,7 +4,7 @@ import './styles/index.scss';
import 'element-plus/theme-chalk/display.css'; import 'element-plus/theme-chalk/display.css';
import 'element-plus/theme-chalk/dark/css-vars.css'; import 'element-plus/theme-chalk/dark/css-vars.css';
import 'nprogress/nprogress.css'; import 'nprogress/nprogress.css';
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import { createApp } from 'vue'; import { createApp } from 'vue';
import App from './App.vue'; import App from './App.vue';
import router, { setupRouter } from './router'; import router, { setupRouter } from './router';
@ -12,8 +12,10 @@ import { setupStore } from '@/store';
import { setupElement, setupDirectives, setupCustomComponents } from '@/plugins'; import { setupElement, setupDirectives, setupCustomComponents } from '@/plugins';
async function bootstrap() { async function bootstrap() {
const app = createApp(App); const app=createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
// 全局完整引入 element 组件 // 全局完整引入 element 组件
setupElement(app); setupElement(app);

View File

@ -1,6 +1,6 @@
import { App } from 'vue'; import { App } from 'vue';
import { permission } from '@/directives/permission'; import { perm,perms } from '@/directives/permission';
import { scrollBar } from '@/directives/scrollBar'; import { scrollBar } from '@/directives/scrollBar';
/** /**
@ -8,6 +8,7 @@ import { scrollBar } from '@/directives/scrollBar';
* @param app * @param app
*/ */
export function setupDirectives(app: App) { export function setupDirectives(app: App) {
app.directive('permission', permission); // 权限控制指令(演示) app.directive('perm', perm); // 权限控制指令 (是否包含其中某个权限)
app.directive('perms', perms); // 权限控制指令 (是否包含所有权限)
app.directive('scrollBar', scrollBar); // 滚动条 app.directive('scrollBar', scrollBar); // 滚动条
} }

View File

@ -8,6 +8,11 @@ const modules: any = import.meta.glob('./modules/**/*.ts', { eager: true });
const routeModuleList: RouteRecordRaw[] = []; 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) => { Object.keys(modules).forEach((key) => {
const mod = modules[key].default || {}; const mod = modules[key].default || {};
const modList = Array.isArray(mod) ? [...mod] : [mod]; const modList = Array.isArray(mod) ? [...mod] : [mod];

233
src/utils/auth.ts Normal file
View 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
View 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
}
}

View 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>

View File

@ -1,258 +1,154 @@
<template> <template>
<PageWrapper title="菜单权限管理"> <div class="menu-index">
<el-row :gutter="10" class="mt-3"> <el-card :bordered="false" class="pt-3 mb-3 proCard">
<el-col :xs="24" :sm="24" :md="12" :lg="6" :xl="6"> <div>
<el-card shadow="hover" class="border-0" size="small"> <el-button type="primary" @click="handleAdd()">
<template #header> <template #icon>
<el-space> <el-icon>
<el-dropdown trigger="hover" @command="selectAddMenu"> <plus />
<el-button type="primary" ghost icon-placement="right"> </el-icon>
添加菜单
<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> </template>
<div class="w-full menu"> 新增
<el-input type="input" v-model="filterText" placeholder="输入菜单名称搜索"> </el-button>
<template #suffix> <el-button @click="handleExpand" v-perm="['sys:user:add']"> 展开/折叠</el-button>
<el-icon class="cursor-pointer el-input__icon" :size="18"> </div>
<SearchOutlined /> </el-card>
</el-icon> <el-card :bordered="false" class="pt-3 mb-3 proCard">
</template> <el-table border v-loading="loading" ref="tableRef" :data="lists" row-key="id"
</el-input> :tree-props="{ children: 'children', hasChildren: 'hasChildren' }">
<div class="py-3 menu-list" v-loading="loading"> <el-table-column label="菜单名称" prop="name" min-width="150" show-overflow-tooltip/>
<el-tree <el-table-column align="center" label="类型" prop="type" min-width="80">
ref="treeRef" <template #default="{ row }">
show-checkbox <div v-if="row.type ==0">菜单</div>
node-key="key" <div v-else-if="row.type == 1">按钮</div>
: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> </template>
<el-alert type="info" closable> 从菜单列表选择一项后进行编辑</el-alert> </el-table-column>
<el-form <el-table-column align="center" label="图标" prop="icon" min-width="80">
:model="formParams" <template #default="{ row }">
:rules="rules" <icon :name="row.icon" :size="20"/>
ref="formRef" </template>
label-placement="left" </el-table-column>
:label-width="100" <el-table-column align="center" label="权限标识" prop="permission" min-width="150" show-overflow-tooltip/>
v-if="isEditMenu" <el-table-column align="center" label="状态" prop="status" min-width="100">
class="py-4" <template #default="{ row }">
> <el-tag v-if="row.status == 0">正常</el-tag>
<el-form-item label="类型" prop="type"> <el-tag v-else type="danger">停用</el-tag>
<span>{{ formParams.type === 1 ? '侧边栏菜单' : '' }}</span> </template>
</el-form-item> </el-table-column>
<el-form-item label="标题" prop="label"> <el-table-column align="center" label="排序" prop="sort" min-width="100"/>
<el-input placeholder="请输入标题" v-model="formParams.label" /> <el-table-column align="center" label="更新时间" prop="updateTime" min-width="180"></el-table-column>
</el-form-item> <el-table-column align="center" label="操作" width="160" fixed="right">
<el-form-item label="副标题" prop="subtitle"> <template #default="{ row }">
<el-input placeholder="请输入副标题" v-model="formParams.subtitle" /> <el-button v-if="row.type !== 1" type="primary" link
</el-form-item> @click="handleAdd(row.id)">
<el-form-item label="路径" prop="path"> 新增
<el-input placeholder="请输入路径" v-model="formParams.path" /> </el-button>
</el-form-item> <el-button type="primary" link @click="handleEdit(row)">
<el-form-item label="打开方式" prop="openType"> 编辑
<el-radio-group v-model="formParams.openType" name="openType"> </el-button>
<el-radio :label="1">当前窗口</el-radio> <el-button type="danger" link @click="handleDelete(row.id)">
<el-radio :label="2">新窗口</el-radio> 删除
</el-radio-group> </el-button>
</el-form-item> </template>
<el-form-item label="菜单权限" prop="auth"> </el-table-column>
<el-input placeholder="请输入权限,多个权限用,分割" v-model="formParams.auth" /> </el-table>
</el-form-item> </el-card>
<el-form-item> <editDialog
<el-space> ref="editRef"
<el-button type="primary" :loading="subLoading" @click="formSubmit(formRef)" v-if="editVisible"
>保存修改</el-button :menuId="menuId"
> :pid="pid"
<el-button @click="resetForm(formRef)">重置</el-button> v-model:visible="editVisible"
</el-space> @success="getLists"
</el-form-item> >
</el-form> </editDialog>
</el-card> </div>
</el-col>
</el-row>
<CreateDrawer ref="createDrawerRef" :title="drawerTitle" />
</PageWrapper>
</template> </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 { const tableRef = shallowRef<InstanceType<typeof ElTable>>();
id: number; import {confirm, message,buildTree} from "@/utils/auth";
label: string;
children?: Tree[];
}
const formRef = ref<FormInstance>(); const editDialog = defineAsyncComponent(() =>
import('./edit.vue')
)
const rules = { const isExpand = ref(false);
label: { const loading = ref(false);
required: true, const editVisible=ref(false);
message: '请输入标题', const menuId=ref(0);
trigger: 'blur', const pid=ref(0)
}, const lists = ref([]);
path: {
required: true,
message: '请输入路径',
trigger: 'blur',
},
};
const createDrawerRef = ref(); const getLists = async () => {
loading.value = true;
let treeItemKey = ref([]); try {
const data = await getMenuList();
const treeData = ref([]); lists.value = buildTree(data);
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; 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> </script>
<style scoped>
</style>

View 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>

View File

@ -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>

View 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>

View File

@ -1,7 +1,11 @@
<template> <template>
<PageWrapper> <PageWrapper>
<el-card :bordered="false" class="pt-3 mb-3 proCard"> <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>
<el-card :bordered="false" class="proCard"> <el-card :bordered="false" class="proCard">
<BasicTable <BasicTable
@ -10,7 +14,7 @@
:row-key="(row) => row.id" :row-key="(row) => row.id"
ref="basicTableRef" ref="basicTableRef"
:actionColumn="actionColumn" :actionColumn="actionColumn"
@checked-row-change="onCheckedRow" @selection-change="onSelectionChange"
scroll-x="1200" scroll-x="1200"
virtual-scroll virtual-scroll
> >
@ -18,67 +22,55 @@
<el-space> <el-space>
<el-button type="primary" @click="addUser"> <el-button type="primary" @click="addUser">
<template #icon> <template #icon>
<el-icon class="el-input__icon"> <el-icon style="vertical-align: middle">
<PlusOutlined /> <plus />
</el-icon> </el-icon>
</template> </template>
新建 新建
</el-button> </el-button>
<el-button type="danger" @click="openRemoveModal" :disabled="!rowKeys.length"> <el-button type="danger" @click="handleDelete" :disabled="!selectionData.length">
<template #icon> <template #icon>
<el-icon class="el-input__icon"> <el-icon class="el-input__icon">
<DeleteOutlined /> <Delete />
</el-icon> </el-icon>
</template> </template>
删除 删除
</el-button> </el-button>
<el-button @click="addTable">
<template #icon>
<el-icon class="el-input__icon">
<ToTopOutlined />
</el-icon>
</template>
导入
</el-button>
</el-space> </el-space>
</template> </template>
</BasicTable> </BasicTable>
</el-card> </el-card>
<editDialog
<basicModal @register="lightModalRegister" ref="modalRef" @ok="removeOkModal"> v-if="editVisible"
<template #default> :userId="userId"
<p class="text-gray-600" style="padding-left: 35px" :roleList="roleList"
>您确认要删除用户<span strong>{{ rowKeysName }} ?</span></p v-model:visible="editVisible"
> @success="reloadTable"
</template> >
</basicModal> </editDialog>
<CreateModal ref="createModalRef" :title="createModalTitle" :isEdit="isEdit" />
</PageWrapper> </PageWrapper>
</template> </template>
<script lang="ts" setup> <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 { ColProps, ElMessage } from 'element-plus';
import { BasicTable, TableAction } from '@/components/Table'; import { BasicTable, TableAction } from '@/components/Table';
import { BasicForm, useForm } from '@/components/Form/index'; 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 { 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'; import { schemas } from './querySchemas';
const userId = ref(0);
const basicTableRef = ref(); const basicTableRef = ref();
const createModalRef = ref(); const editVisible = ref(false)
const rowKeys = ref([]); const roleList = ref([])
const rowKeysName = ref([]); const selectionData = ref([])
const tableData = ref(); const tableData = ref();
const isEdit = ref(false); const editDialog = defineAsyncComponent(() =>
const createModalTitle = ref('添加用户'); import('./edit.vue')
)
const showModal = ref(false);
const formParams = reactive({ const formParams = reactive({
username: '', username: '',
mobile: '', mobile: '',
@ -130,49 +122,29 @@
}, },
}); });
function addTable() {
showModal.value = true;
}
const loadDataTable = async (res) => { const loadDataTable = async (res) => {
const result = await getUserList({ ...formParams, ...params.value, ...res }); const result = await getUserList({ ...formParams, ...params.value, ...res });
tableData.value = result.records; tableData.value = result.records;
return result; 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() { function reloadTable() {
basicTableRef.value.reload(); basicTableRef.value.reload();
} }
function handleEdit(record: Recordable) { async function handleEdit(record: Recordable) {
record.mobile = parseInt(record.mobile); userId.value=record.row.id
addUser(); await nextTick();
nextTick(() => { editVisible.value=true
isEdit.value = true;
createModalRef.value.setProps({ title: '编辑用户' });
createModalRef.value.setFieldsValue({
...unref(record),
});
});
} }
function handleDelete(record: Recordable) { async function handleDelete(record: Recordable) {
rowKeysName.value = record.row.username; await confirm('确定要删除?');
openRemoveModal(); selectionData.value.length>0? await userBatchDelete(selectionData.value.join()):await userDelete(record.row.id);
message("删除成功");
reloadTable()
} }
function handleSubmit(values: Recordable) { function handleSubmit(values: Recordable) {
for (const key in formParams) { for (const key in formParams) {
if (values[key] != null && values[key] != undefined) { if (values[key] != null && values[key] != undefined) {
@ -187,7 +159,9 @@
formParams[key] =''; formParams[key] ='';
} }
} }
function onSelectionChange(value){
selectionData.value = value
}
const [register, {}] = useForm({ const [register, {}] = useForm({
labelWidth: 80, labelWidth: 80,
layout: 'horizontal', layout: 'horizontal',
@ -196,17 +170,6 @@
schemas schemas
}); });
const [
lightModalRegister,
{ openModal: lightOpenModal, closeModal: lightCloseModal, setSubLoading: lightSetSubLoading },
] = useModal({
title: '删除确认',
showIcon: true,
type: 'warning',
closable: false,
maskClosable: true,
width: 380,
});
// //
function openRemoveModal() { function openRemoveModal() {
@ -215,17 +178,23 @@
// //
function addUser() { function addUser() {
isEdit.value = false; userId.value =0
createModalRef.value.setProps({ title: '添加用户' }); editVisible.value = true
createModalRef.value.openModal();
} }
// //
function removeOkModal() { function removeOkModal() {
lightSetSubLoading(true);
lightCloseModal(); lightCloseModal();
lightSetSubLoading(); reloadTable()
ElMessage.error('抱歉,您没有操作权限');
} }
const getAllDict = async () => {
const list = await getRoleAllList();
roleList.value = list ? list : [];
};
onMounted(() => {
getAllDict()
});
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

View File

@ -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: '请输入登录账号',
},
},
];