fix: 🧩 路由相关修改和菜单图标缺陷修复,代码生成器修复
This commit is contained in:
parent
575770ffeb
commit
bf0d3f9c66
|
@ -0,0 +1,30 @@
|
|||
import { http } from '@/api/service/request';
|
||||
import type { BaseResult, ResultTable } from '@/api/service/types';
|
||||
|
||||
/**
|
||||
* 系统菜单图标---获取多语言列表
|
||||
*/
|
||||
export const fetchGetMenuIconList = (data: any) => {
|
||||
return http.request<BaseResult<ResultTable>>('get', `menuIcon/getMenuIconList/${data.currentPage}/${data.pageSize}`, { params: data });
|
||||
};
|
||||
|
||||
/**
|
||||
* 系统菜单图标---添加多语言
|
||||
*/
|
||||
export const fetchAddMenuIcon = (data: any) => {
|
||||
return http.request<BaseResult<object>>('post', 'menuIcon/addMenuIcon', { data });
|
||||
};
|
||||
|
||||
/**
|
||||
* 系统菜单图标---更新多语言
|
||||
*/
|
||||
export const fetchUpdateMenuIcon = (data: any) => {
|
||||
return http.request<BaseResult<object>>('put', 'menuIcon/updateMenuIcon', { data });
|
||||
};
|
||||
|
||||
/**
|
||||
* 系统菜单图标---删除多语言
|
||||
*/
|
||||
export const fetchDeleteMenuIcon = (data: any) => {
|
||||
return http.request<BaseResult<object>>('delete', 'menuIcon/deleteMenuIcon', { data });
|
||||
};
|
|
@ -12,8 +12,8 @@ export const getMenuIconList = (data: any) => {
|
|||
};
|
||||
|
||||
/** 菜单管理-列表 */
|
||||
export const getMenuList = (data?: any) => {
|
||||
return http.request<BaseResult<ResultTable>>('get', `router/getMenus`, { data });
|
||||
export const getMenusList = (data?: any) => {
|
||||
return http.request<BaseResult<ResultTable>>('get', `router/getMenusList`, { params: data });
|
||||
};
|
||||
|
||||
/** 菜单管理-添加菜单 */
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { iconType } from "./types";
|
||||
import { h, defineComponent, type Component } from "vue";
|
||||
import { IconifyIconOnline, IconifyIconOffline, FontIcon } from "../index";
|
||||
import type { iconType } from './types';
|
||||
import { type Component, defineComponent, h } from 'vue';
|
||||
import { FontIcon, IconifyIconOffline, IconifyIconOnline } from '../index';
|
||||
|
||||
/**
|
||||
* 支持 `iconfont`、自定义 `svg` 以及 `iconify` 中所有的图标
|
||||
|
@ -10,52 +10,48 @@ import { IconifyIconOnline, IconifyIconOffline, FontIcon } from "../index";
|
|||
* @returns Component
|
||||
*/
|
||||
export function useRenderIcon(icon: any, attrs?: iconType): Component {
|
||||
// iconfont
|
||||
const ifReg = /^IF-/;
|
||||
// typeof icon === "function" 属于SVG
|
||||
if (ifReg.test(icon)) {
|
||||
// iconfont
|
||||
const name = icon.split(ifReg)[1];
|
||||
const iconName = name.slice(
|
||||
0,
|
||||
name.indexOf(" ") == -1 ? name.length : name.indexOf(" ")
|
||||
);
|
||||
const iconType = name.slice(name.indexOf(" ") + 1, name.length);
|
||||
return defineComponent({
|
||||
name: "FontIcon",
|
||||
render() {
|
||||
return h(FontIcon, {
|
||||
icon: iconName,
|
||||
iconType,
|
||||
...attrs
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (typeof icon === "function" || typeof icon?.render === "function") {
|
||||
// svg
|
||||
return attrs ? h(icon, { ...attrs }) : icon;
|
||||
} else if (typeof icon === "object") {
|
||||
return defineComponent({
|
||||
name: "OfflineIcon",
|
||||
render() {
|
||||
return h(IconifyIconOffline, {
|
||||
icon: icon,
|
||||
...attrs
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 通过是否存在 : 符号来判断是在线还是本地图标,存在即是在线图标,反之
|
||||
return defineComponent({
|
||||
name: "Icon",
|
||||
render() {
|
||||
const IconifyIcon =
|
||||
icon && icon.includes(":") ? IconifyIconOnline : IconifyIconOffline;
|
||||
return h(IconifyIcon, {
|
||||
icon: icon,
|
||||
...attrs
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
// iconfont
|
||||
const ifReg = /^IF-/;
|
||||
// typeof icon === "function" 属于SVG
|
||||
if (ifReg.test(icon)) {
|
||||
// iconfont
|
||||
const name = icon.split(ifReg)[1];
|
||||
const iconName = name.slice(0, name.indexOf(' ') == -1 ? name.length : name.indexOf(' '));
|
||||
const iconType = name.slice(name.indexOf(' ') + 1, name.length);
|
||||
return defineComponent({
|
||||
name: 'FontIcon',
|
||||
render() {
|
||||
return h(FontIcon, {
|
||||
icon: iconName,
|
||||
iconType,
|
||||
...attrs,
|
||||
});
|
||||
},
|
||||
});
|
||||
} else if (typeof icon === 'function' || typeof icon?.render === 'function') {
|
||||
// svg
|
||||
return attrs ? h(icon, { ...attrs }) : icon;
|
||||
} else if (typeof icon === 'object') {
|
||||
return defineComponent({
|
||||
name: 'OfflineIcon',
|
||||
render() {
|
||||
return h(IconifyIconOffline, {
|
||||
icon: icon,
|
||||
...attrs,
|
||||
});
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// 通过是否存在 : 符号来判断是在线还是本地图标,存在即是在线图标,反之
|
||||
return defineComponent({
|
||||
name: 'Icon',
|
||||
render() {
|
||||
const IconifyIcon = icon && icon.includes(':') ? IconifyIconOnline : IconifyIconOffline;
|
||||
return h(IconifyIcon, {
|
||||
icon: icon,
|
||||
...attrs,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,15 +0,0 @@
|
|||
import iconifyIconOffline from "./src/iconifyIconOffline";
|
||||
import iconifyIconOnline from "./src/iconifyIconOnline";
|
||||
import iconSelect from "./src/Select.vue";
|
||||
import fontIcon from "./src/iconfont";
|
||||
|
||||
/** 本地图标组件 */
|
||||
const IconifyIconOffline = iconifyIconOffline;
|
||||
/** 在线图标组件 */
|
||||
const IconifyIconOnline = iconifyIconOnline;
|
||||
/** `IconSelect`图标选择器组件 */
|
||||
const IconSelect = iconSelect;
|
||||
/** `iconfont`组件 */
|
||||
const FontIcon = fontIcon;
|
||||
|
||||
export { IconifyIconOffline, IconifyIconOnline, IconSelect, FontIcon };
|
|
@ -1,222 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { IconJson } from '@/components/ReIcon/data';
|
||||
import { cloneDeep, isAllEmpty } from '@pureadmin/utils';
|
||||
import { computed, CSSProperties, ref, watch } from 'vue';
|
||||
import Search from '@iconify-icons/ri/search-eye-line';
|
||||
|
||||
type ParameterCSSProperties = (item?: string) => CSSProperties | undefined;
|
||||
|
||||
defineOptions({
|
||||
name: 'IconSelect',
|
||||
});
|
||||
|
||||
const inputValue = defineModel({ type: String });
|
||||
|
||||
const iconList = ref(IconJson);
|
||||
const icon = ref();
|
||||
const currentActiveType = ref('ep:');
|
||||
// 深拷贝图标数据,前端做搜索
|
||||
const copyIconList = cloneDeep(iconList.value);
|
||||
const totalPage = ref(0);
|
||||
// 每页显示35个图标
|
||||
const pageSize = ref(35);
|
||||
const currentPage = ref(1);
|
||||
|
||||
// 搜索条件
|
||||
const filterValue = ref('');
|
||||
|
||||
const tabsList = [
|
||||
{
|
||||
label: 'Element Plus',
|
||||
name: 'ep:',
|
||||
},
|
||||
{
|
||||
label: 'Remix Icon',
|
||||
name: 'ri:',
|
||||
},
|
||||
{
|
||||
label: 'Font Awesome 5 Solid',
|
||||
name: 'fa-solid:',
|
||||
},
|
||||
];
|
||||
|
||||
const pageList = computed(() => copyIconList[currentActiveType.value].filter(i => i.includes(filterValue.value)).slice((currentPage.value - 1) * pageSize.value, currentPage.value * pageSize.value));
|
||||
|
||||
const iconItemStyle = computed((): ParameterCSSProperties => {
|
||||
return item => {
|
||||
if (inputValue.value === currentActiveType.value + item) {
|
||||
return {
|
||||
borderColor: 'var(--el-color-primary)',
|
||||
color: 'var(--el-color-primary)',
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
function setVal() {
|
||||
currentActiveType.value = inputValue.value.substring(0, inputValue.value.indexOf(':') + 1);
|
||||
icon.value = inputValue.value.substring(inputValue.value.indexOf(':') + 1);
|
||||
}
|
||||
|
||||
function onBeforeEnter() {
|
||||
if (isAllEmpty(icon.value)) return;
|
||||
setVal();
|
||||
// 寻找当前图标在第几页
|
||||
const curIconIndex = copyIconList[currentActiveType.value].findIndex(i => i === icon.value);
|
||||
currentPage.value = Math.ceil((curIconIndex + 1) / pageSize.value);
|
||||
}
|
||||
|
||||
function onAfterLeave() {
|
||||
filterValue.value = '';
|
||||
}
|
||||
|
||||
function handleClick({ props }) {
|
||||
currentPage.value = 1;
|
||||
currentActiveType.value = props.name;
|
||||
}
|
||||
|
||||
function onChangeIcon(item) {
|
||||
icon.value = item;
|
||||
inputValue.value = currentActiveType.value + item;
|
||||
}
|
||||
|
||||
function onCurrentChange(page) {
|
||||
currentPage.value = page;
|
||||
}
|
||||
|
||||
function onClear() {
|
||||
icon.value = '';
|
||||
inputValue.value = '';
|
||||
}
|
||||
|
||||
watch(
|
||||
() => pageList.value,
|
||||
() => (totalPage.value = copyIconList[currentActiveType.value].filter(i => i.includes(filterValue.value)).length),
|
||||
{ immediate: true },
|
||||
);
|
||||
watch(
|
||||
() => inputValue.value,
|
||||
val => val && setVal(),
|
||||
{ immediate: true },
|
||||
);
|
||||
watch(
|
||||
() => filterValue.value,
|
||||
() => (currentPage.value = 1),
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="selector">
|
||||
<el-input v-model="inputValue" disabled>
|
||||
<template #append>
|
||||
<el-popover
|
||||
:popper-options="{
|
||||
placement: 'auto',
|
||||
}"
|
||||
:width="350"
|
||||
popper-class="pure-popper"
|
||||
trigger="click"
|
||||
@before-enter="onBeforeEnter"
|
||||
@after-leave="onAfterLeave"
|
||||
>
|
||||
<template #reference>
|
||||
<div class="w-[40px] h-[32px] cursor-pointer flex justify-center items-center">
|
||||
<IconifyIconOffline v-if="!icon" :icon="Search" />
|
||||
<IconifyIconOnline v-else :icon="inputValue" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-input v-model="filterValue" class="px-2 pt-2" clearable placeholder="搜索图标" />
|
||||
|
||||
<el-tabs v-model="currentActiveType" @tab-click="handleClick">
|
||||
<el-tab-pane v-for="(pane, index) in tabsList" :key="index" :label="pane.label" :name="pane.name">
|
||||
<el-scrollbar height="220px">
|
||||
<ul class="flex flex-wrap px-2 ml-2">
|
||||
<li
|
||||
v-for="(item, key) in pageList"
|
||||
:key="key"
|
||||
:style="iconItemStyle(item)"
|
||||
:title="item"
|
||||
class="icon-item p-2 cursor-pointer mr-2 mt-1 flex justify-center items-center border border-[#e5e7eb]"
|
||||
@click="onChangeIcon(item)"
|
||||
>
|
||||
<IconifyIconOnline :icon="currentActiveType + item" height="20px" width="20px" />
|
||||
</li>
|
||||
</ul>
|
||||
<el-empty v-show="pageList.length === 0" :description="`${filterValue} 图标不存在`" :image-size="60" />
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<div class="w-full h-9 flex items-center overflow-auto border-t border-[#e5e7eb]">
|
||||
<el-pagination
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:pager-count="5"
|
||||
:total="totalPage"
|
||||
background
|
||||
class="flex-auto ml-2"
|
||||
layout="pager"
|
||||
size="small"
|
||||
@current-change="onCurrentChange"
|
||||
/>
|
||||
<el-button bg class="justify-end mr-2 ml-2" size="small" text type="danger" @click="onClear"> 清空 </el-button>
|
||||
</div>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icon-item {
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
transition: all 0.4s;
|
||||
transform: scaleX(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav-next) {
|
||||
font-size: 15px;
|
||||
line-height: 32px;
|
||||
box-shadow: -5px 0 5px -6px #ccc;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav-prev) {
|
||||
font-size: 15px;
|
||||
line-height: 32px;
|
||||
box-shadow: 5px 0 5px -6px #ccc;
|
||||
}
|
||||
|
||||
:deep(.el-input-group__append) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item) {
|
||||
height: 30px;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__header),
|
||||
:deep(.el-tabs__nav-wrap) {
|
||||
position: static;
|
||||
margin: 0;
|
||||
box-shadow: 0 2px 5px rgb(0 0 0 / 6%);
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav-wrap::after) {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav-wrap) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__content) {
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
|
@ -1,61 +0,0 @@
|
|||
import type { iconType } from "./types";
|
||||
import { h, defineComponent, type Component } from "vue";
|
||||
import { IconifyIconOnline, IconifyIconOffline, FontIcon } from "../index";
|
||||
|
||||
/**
|
||||
* 支持 `iconfont`、自定义 `svg` 以及 `iconify` 中所有的图标
|
||||
* @see 点击查看文档图标篇 {@link https://pure-admin.github.io/pure-admin-doc/pages/icon/}
|
||||
* @param icon 必传 图标
|
||||
* @param attrs 可选 iconType 属性
|
||||
* @returns Component
|
||||
*/
|
||||
export function useRenderIcon(icon: any, attrs?: iconType): Component {
|
||||
// iconfont
|
||||
const ifReg = /^IF-/;
|
||||
// typeof icon === "function" 属于SVG
|
||||
if (ifReg.test(icon)) {
|
||||
// iconfont
|
||||
const name = icon.split(ifReg)[1];
|
||||
const iconName = name.slice(
|
||||
0,
|
||||
name.indexOf(" ") == -1 ? name.length : name.indexOf(" ")
|
||||
);
|
||||
const iconType = name.slice(name.indexOf(" ") + 1, name.length);
|
||||
return defineComponent({
|
||||
name: "FontIcon",
|
||||
render() {
|
||||
return h(FontIcon, {
|
||||
icon: iconName,
|
||||
iconType,
|
||||
...attrs
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (typeof icon === "function" || typeof icon?.render === "function") {
|
||||
// svg
|
||||
return attrs ? h(icon, { ...attrs }) : icon;
|
||||
} else if (typeof icon === "object") {
|
||||
return defineComponent({
|
||||
name: "OfflineIcon",
|
||||
render() {
|
||||
return h(IconifyIconOffline, {
|
||||
icon: icon,
|
||||
...attrs
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 通过是否存在 : 符号来判断是在线还是本地图标,存在即是在线图标,反之
|
||||
return defineComponent({
|
||||
name: "Icon",
|
||||
render() {
|
||||
const IconifyIcon =
|
||||
icon && icon.includes(":") ? IconifyIconOnline : IconifyIconOffline;
|
||||
return h(IconifyIcon, {
|
||||
icon: icon,
|
||||
...attrs
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
import { h, defineComponent } from "vue";
|
||||
|
||||
// 封装iconfont组件,默认`font-class`引用模式,支持`unicode`引用、`font-class`引用、`symbol`引用 (https://www.iconfont.cn/help/detail?spm=a313x.7781069.1998910419.20&helptype=code)
|
||||
export default defineComponent({
|
||||
name: "FontIcon",
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const attrs = this.$attrs;
|
||||
if (Object.keys(attrs).includes("uni") || attrs?.iconType === "uni") {
|
||||
return h(
|
||||
"i",
|
||||
{
|
||||
class: "iconfont",
|
||||
...attrs
|
||||
},
|
||||
this.icon
|
||||
);
|
||||
} else if (
|
||||
Object.keys(attrs).includes("svg") ||
|
||||
attrs?.iconType === "svg"
|
||||
) {
|
||||
return h(
|
||||
"svg",
|
||||
{
|
||||
class: "icon-svg",
|
||||
"aria-hidden": true
|
||||
},
|
||||
{
|
||||
default: () => [
|
||||
h("use", {
|
||||
"xlink:href": `#${this.icon}`
|
||||
})
|
||||
]
|
||||
}
|
||||
);
|
||||
} else {
|
||||
return h("i", {
|
||||
class: `iconfont ${this.icon}`,
|
||||
...attrs
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,30 +0,0 @@
|
|||
import { h, defineComponent } from "vue";
|
||||
import { Icon as IconifyIcon, addIcon } from "@iconify/vue/dist/offline";
|
||||
|
||||
// Iconify Icon在Vue里本地使用(用于内网环境)
|
||||
export default defineComponent({
|
||||
name: "IconifyIconOffline",
|
||||
components: { IconifyIcon },
|
||||
props: {
|
||||
icon: {
|
||||
default: null
|
||||
}
|
||||
},
|
||||
render() {
|
||||
if (typeof this.icon === "object") addIcon(this.icon, this.icon);
|
||||
const attrs = this.$attrs;
|
||||
return h(
|
||||
IconifyIcon,
|
||||
{
|
||||
icon: this.icon,
|
||||
style: attrs?.style
|
||||
? Object.assign(attrs.style, { outline: "none" })
|
||||
: { outline: "none" },
|
||||
...attrs
|
||||
},
|
||||
{
|
||||
default: () => []
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,30 +0,0 @@
|
|||
import { h, defineComponent } from "vue";
|
||||
import { Icon as IconifyIcon } from "@iconify/vue";
|
||||
|
||||
// Iconify Icon在Vue里在线使用(用于外网环境)
|
||||
export default defineComponent({
|
||||
name: "IconifyIconOnline",
|
||||
components: { IconifyIcon },
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const attrs = this.$attrs;
|
||||
return h(
|
||||
IconifyIcon,
|
||||
{
|
||||
icon: `${this.icon}`,
|
||||
style: attrs?.style
|
||||
? Object.assign(attrs.style, { outline: "none" })
|
||||
: { outline: "none" },
|
||||
...attrs
|
||||
},
|
||||
{
|
||||
default: () => []
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,70 +0,0 @@
|
|||
// 这里存放本地图标,在 src/layout/index.vue 文件中加载,避免在首启动加载
|
||||
import { addIcon } from "@iconify/vue/dist/offline";
|
||||
|
||||
// 本地菜单图标,后端在路由的 icon 中返回对应的图标字符串并且前端在此处使用 addIcon 添加即可渲染菜单图标
|
||||
// @iconify-icons/ep
|
||||
import Menu from "@iconify-icons/ep/menu";
|
||||
import Edit from "@iconify-icons/ep/edit";
|
||||
import SetUp from "@iconify-icons/ep/set-up";
|
||||
import Guide from "@iconify-icons/ep/guide";
|
||||
import Monitor from "@iconify-icons/ep/monitor";
|
||||
import Lollipop from "@iconify-icons/ep/lollipop";
|
||||
import Histogram from "@iconify-icons/ep/histogram";
|
||||
import HomeFilled from "@iconify-icons/ep/home-filled";
|
||||
addIcon("ep:menu", Menu);
|
||||
addIcon("ep:edit", Edit);
|
||||
addIcon("ep:set-up", SetUp);
|
||||
addIcon("ep:guide", Guide);
|
||||
addIcon("ep:monitor", Monitor);
|
||||
addIcon("ep:lollipop", Lollipop);
|
||||
addIcon("ep:histogram", Histogram);
|
||||
addIcon("ep:home-filled", HomeFilled);
|
||||
// @iconify-icons/ri
|
||||
import Tag from "@iconify-icons/ri/bookmark-2-line";
|
||||
import Ppt from "@iconify-icons/ri/file-ppt-2-line";
|
||||
import Card from "@iconify-icons/ri/bank-card-line";
|
||||
import Role from "@iconify-icons/ri/admin-fill";
|
||||
import Info from "@iconify-icons/ri/file-info-line";
|
||||
import Dept from "@iconify-icons/ri/git-branch-line";
|
||||
import Table from "@iconify-icons/ri/table-line";
|
||||
import Links from "@iconify-icons/ri/links-fill";
|
||||
import Search from "@iconify-icons/ri/search-line";
|
||||
import FlUser from "@iconify-icons/ri/admin-line";
|
||||
import Setting from "@iconify-icons/ri/settings-3-line";
|
||||
import MindMap from "@iconify-icons/ri/mind-map";
|
||||
import BarChart from "@iconify-icons/ri/bar-chart-horizontal-line";
|
||||
import LoginLog from "@iconify-icons/ri/window-line";
|
||||
import Artboard from "@iconify-icons/ri/artboard-line";
|
||||
import SystemLog from "@iconify-icons/ri/file-search-line";
|
||||
import ListCheck from "@iconify-icons/ri/list-check";
|
||||
import UbuntuFill from "@iconify-icons/ri/ubuntu-fill";
|
||||
import OnlineUser from "@iconify-icons/ri/user-voice-line";
|
||||
import EditBoxLine from "@iconify-icons/ri/edit-box-line";
|
||||
import OperationLog from "@iconify-icons/ri/history-fill";
|
||||
import InformationLine from "@iconify-icons/ri/information-line";
|
||||
import TerminalWindowLine from "@iconify-icons/ri/terminal-window-line";
|
||||
import CheckboxCircleLine from "@iconify-icons/ri/checkbox-circle-line";
|
||||
addIcon("ri:bookmark-2-line", Tag);
|
||||
addIcon("ri:file-ppt-2-line", Ppt);
|
||||
addIcon("ri:bank-card-line", Card);
|
||||
addIcon("ri:admin-fill", Role);
|
||||
addIcon("ri:file-info-line", Info);
|
||||
addIcon("ri:git-branch-line", Dept);
|
||||
addIcon("ri:links-fill", Links);
|
||||
addIcon("ri:table-line", Table);
|
||||
addIcon("ri:search-line", Search);
|
||||
addIcon("ri:admin-line", FlUser);
|
||||
addIcon("ri:settings-3-line", Setting);
|
||||
addIcon("ri:mind-map", MindMap);
|
||||
addIcon("ri:bar-chart-horizontal-line", BarChart);
|
||||
addIcon("ri:window-line", LoginLog);
|
||||
addIcon("ri:file-search-line", SystemLog);
|
||||
addIcon("ri:artboard-line", Artboard);
|
||||
addIcon("ri:list-check", ListCheck);
|
||||
addIcon("ri:ubuntu-fill", UbuntuFill);
|
||||
addIcon("ri:user-voice-line", OnlineUser);
|
||||
addIcon("ri:edit-box-line", EditBoxLine);
|
||||
addIcon("ri:history-fill", OperationLog);
|
||||
addIcon("ri:information-line", InformationLine);
|
||||
addIcon("ri:terminal-window-line", TerminalWindowLine);
|
||||
addIcon("ri:checkbox-circle-line", CheckboxCircleLine);
|
|
@ -1,20 +0,0 @@
|
|||
export interface iconType {
|
||||
// iconify (https://docs.iconify.design/icon-components/vue/#properties)
|
||||
inline?: boolean;
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
horizontalFlip?: boolean;
|
||||
verticalFlip?: boolean;
|
||||
flip?: string;
|
||||
rotate?: number | string;
|
||||
color?: string;
|
||||
horizontalAlign?: boolean;
|
||||
verticalAlign?: boolean;
|
||||
align?: string;
|
||||
onLoad?: Function;
|
||||
includes?: Function;
|
||||
// svg 需要什么SVG属性自行添加
|
||||
fill?: string;
|
||||
// all icon
|
||||
style?: object;
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import reSegmented from "./src/index";
|
||||
import { withInstall } from "@pureadmin/utils";
|
||||
|
||||
/** 分段控制器组件 */
|
||||
export const ReSegmented = withInstall(reSegmented);
|
||||
|
||||
export default ReSegmented;
|
||||
export type { OptionsType } from "./src/type";
|
|
@ -1,157 +0,0 @@
|
|||
.pure-segmented {
|
||||
--pure-control-padding-horizontal: 12px;
|
||||
--pure-control-padding-horizontal-sm: 8px;
|
||||
--pure-segmented-track-padding: 2px;
|
||||
--pure-segmented-line-width: 1px;
|
||||
|
||||
--pure-segmented-border-radius-small: 4px;
|
||||
--pure-segmented-border-radius-base: 6px;
|
||||
--pure-segmented-border-radius-large: 8px;
|
||||
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
padding: var(--pure-segmented-track-padding);
|
||||
font-size: var(--el-font-size-base);
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
background-color: rgb(0 0 0 / 4%);
|
||||
border-radius: var(--pure-segmented-border-radius-base);
|
||||
}
|
||||
|
||||
.pure-segmented-block {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.pure-segmented-block .pure-segmented-item {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pure-segmented-block .pure-segmented-item > .pure-segmented-item-label > span {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* small */
|
||||
.pure-segmented.pure-segmented--small {
|
||||
border-radius: var(--pure-segmented-border-radius-small);
|
||||
}
|
||||
.pure-segmented.pure-segmented--small .pure-segmented-item {
|
||||
border-radius: var(--el-border-radius-small);
|
||||
}
|
||||
.pure-segmented.pure-segmented--small .pure-segmented-item > div {
|
||||
min-height: calc(
|
||||
var(--el-component-size-small) - var(--pure-segmented-track-padding) * 2
|
||||
);
|
||||
line-height: calc(
|
||||
var(--el-component-size-small) - var(--pure-segmented-track-padding) * 2
|
||||
);
|
||||
padding: 0
|
||||
calc(
|
||||
var(--pure-control-padding-horizontal-sm) -
|
||||
var(--pure-segmented-line-width)
|
||||
);
|
||||
}
|
||||
|
||||
/* large */
|
||||
.pure-segmented.pure-segmented--large {
|
||||
border-radius: var(--pure-segmented-border-radius-large);
|
||||
}
|
||||
.pure-segmented.pure-segmented--large .pure-segmented-item {
|
||||
border-radius: calc(
|
||||
var(--el-border-radius-base) + var(--el-border-radius-small)
|
||||
);
|
||||
}
|
||||
.pure-segmented.pure-segmented--large .pure-segmented-item > div {
|
||||
min-height: calc(
|
||||
var(--el-component-size-large) - var(--pure-segmented-track-padding) * 2
|
||||
);
|
||||
line-height: calc(
|
||||
var(--el-component-size-large) - var(--pure-segmented-track-padding) * 2
|
||||
);
|
||||
padding: 0
|
||||
calc(
|
||||
var(--pure-control-padding-horizontal) - var(--pure-segmented-line-width)
|
||||
);
|
||||
font-size: var(--el-font-size-medium);
|
||||
}
|
||||
|
||||
/* default */
|
||||
.pure-segmented-item {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
border-radius: var(--el-border-radius-base);
|
||||
transition: all 0.1s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
}
|
||||
.pure-segmented .pure-segmented-item > div {
|
||||
min-height: calc(
|
||||
var(--el-component-size) - var(--pure-segmented-track-padding) * 2
|
||||
);
|
||||
line-height: calc(
|
||||
var(--el-component-size) - var(--pure-segmented-track-padding) * 2
|
||||
);
|
||||
padding: 0
|
||||
calc(
|
||||
var(--pure-control-padding-horizontal) - var(--pure-segmented-line-width)
|
||||
);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
transition: 0.1s;
|
||||
}
|
||||
|
||||
.pure-segmented-group {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-items: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pure-segmented-item-selected {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
padding: 4px 0;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow:
|
||||
0 2px 8px -2px rgb(0 0 0 / 5%),
|
||||
0 1px 4px -1px rgb(0 0 0 / 7%),
|
||||
0 0 1px rgb(0 0 0 / 7%);
|
||||
transition:
|
||||
transform 0.5s cubic-bezier(0.645, 0.045, 0.355, 1),
|
||||
width 0.5s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
will-change: transform, width;
|
||||
}
|
||||
|
||||
.pure-segmented-item > input {
|
||||
position: absolute;
|
||||
inset-block-start: 0;
|
||||
inset-inline-start: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.pure-segmented-item-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pure-segmented-item-icon svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.pure-segmented-item-disabled {
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
cursor: not-allowed;
|
||||
}
|
|
@ -1,216 +0,0 @@
|
|||
import "./index.css";
|
||||
import type { OptionsType } from "./type";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import {
|
||||
useDark,
|
||||
isNumber,
|
||||
isFunction,
|
||||
useResizeObserver
|
||||
} from "@pureadmin/utils";
|
||||
import {
|
||||
type PropType,
|
||||
h,
|
||||
ref,
|
||||
toRef,
|
||||
watch,
|
||||
nextTick,
|
||||
defineComponent,
|
||||
getCurrentInstance
|
||||
} from "vue";
|
||||
|
||||
const props = {
|
||||
options: {
|
||||
type: Array<OptionsType>,
|
||||
default: () => []
|
||||
},
|
||||
/** 默认选中,按照第一个索引为 `0` 的模式,可选(`modelValue`只有传`number`类型时才为响应式) */
|
||||
modelValue: {
|
||||
type: undefined,
|
||||
require: false,
|
||||
default: "0"
|
||||
},
|
||||
/** 将宽度调整为父元素宽度 */
|
||||
block: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/** 控件尺寸 */
|
||||
size: {
|
||||
type: String as PropType<"small" | "default" | "large">
|
||||
},
|
||||
/** 是否全局禁用,默认 `false` */
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/** 当内容发生变化时,设置 `resize` 可使其自适应容器位置 */
|
||||
resize: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: "ReSegmented",
|
||||
props,
|
||||
emits: ["change", "update:modelValue"],
|
||||
setup(props, { emit }) {
|
||||
const width = ref(0);
|
||||
const translateX = ref(0);
|
||||
const { isDark } = useDark();
|
||||
const initStatus = ref(false);
|
||||
const curMouseActive = ref(-1);
|
||||
const segmentedItembg = ref("");
|
||||
const instance = getCurrentInstance()!;
|
||||
const curIndex = isNumber(props.modelValue)
|
||||
? toRef(props, "modelValue")
|
||||
: ref(0);
|
||||
|
||||
function handleChange({ option, index }, event: Event) {
|
||||
if (props.disabled || option.disabled) return;
|
||||
event.preventDefault();
|
||||
isNumber(props.modelValue)
|
||||
? emit("update:modelValue", index)
|
||||
: (curIndex.value = index);
|
||||
segmentedItembg.value = "";
|
||||
emit("change", { index, option });
|
||||
}
|
||||
|
||||
function handleMouseenter({ option, index }, event: Event) {
|
||||
if (props.disabled) return;
|
||||
event.preventDefault();
|
||||
curMouseActive.value = index;
|
||||
if (option.disabled || curIndex.value === index) {
|
||||
segmentedItembg.value = "";
|
||||
} else {
|
||||
segmentedItembg.value = isDark.value
|
||||
? "#1f1f1f"
|
||||
: "rgba(0, 0, 0, 0.06)";
|
||||
}
|
||||
}
|
||||
|
||||
function handleMouseleave(_, event: Event) {
|
||||
if (props.disabled) return;
|
||||
event.preventDefault();
|
||||
curMouseActive.value = -1;
|
||||
}
|
||||
|
||||
function handleInit(index = curIndex.value) {
|
||||
nextTick(() => {
|
||||
const curLabelRef = instance?.proxy?.$refs[`labelRef${index}`] as ElRef;
|
||||
if (!curLabelRef) return;
|
||||
width.value = curLabelRef.clientWidth;
|
||||
translateX.value = curLabelRef.offsetLeft;
|
||||
initStatus.value = true;
|
||||
});
|
||||
}
|
||||
|
||||
function handleResizeInit() {
|
||||
useResizeObserver(".pure-segmented", () => {
|
||||
nextTick(() => {
|
||||
handleInit(curIndex.value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
(props.block || props.resize) && handleResizeInit();
|
||||
|
||||
watch(
|
||||
() => curIndex.value,
|
||||
index => {
|
||||
nextTick(() => {
|
||||
handleInit(index);
|
||||
});
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
watch(() => props.size, handleResizeInit, {
|
||||
immediate: true
|
||||
});
|
||||
|
||||
const rendLabel = () => {
|
||||
return props.options.map((option, index) => {
|
||||
return (
|
||||
<label
|
||||
ref={`labelRef${index}`}
|
||||
class={[
|
||||
"pure-segmented-item",
|
||||
(props.disabled || option?.disabled) &&
|
||||
"pure-segmented-item-disabled"
|
||||
]}
|
||||
style={{
|
||||
background:
|
||||
curMouseActive.value === index ? segmentedItembg.value : "",
|
||||
color: props.disabled
|
||||
? null
|
||||
: !option.disabled &&
|
||||
(curIndex.value === index || curMouseActive.value === index)
|
||||
? isDark.value
|
||||
? "rgba(255, 255, 255, 0.85)"
|
||||
: "rgba(0,0,0,.88)"
|
||||
: ""
|
||||
}}
|
||||
onMouseenter={event => handleMouseenter({ option, index }, event)}
|
||||
onMouseleave={event => handleMouseleave({ option, index }, event)}
|
||||
onClick={event => handleChange({ option, index }, event)}
|
||||
>
|
||||
<input type="radio" name="segmented" />
|
||||
<div
|
||||
class="pure-segmented-item-label"
|
||||
v-tippy={{
|
||||
content: option?.tip,
|
||||
zIndex: 41000
|
||||
}}
|
||||
>
|
||||
{option.icon && !isFunction(option.label) ? (
|
||||
<span
|
||||
class="pure-segmented-item-icon"
|
||||
style={{ marginRight: option.label ? "6px" : 0 }}
|
||||
>
|
||||
{h(
|
||||
useRenderIcon(option.icon, {
|
||||
...option?.iconAttrs
|
||||
})
|
||||
)}
|
||||
</span>
|
||||
) : null}
|
||||
{option.label ? (
|
||||
isFunction(option.label) ? (
|
||||
h(option.label)
|
||||
) : (
|
||||
<span>{option.label}</span>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
</label>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return () => (
|
||||
<div
|
||||
class={{
|
||||
"pure-segmented": true,
|
||||
"pure-segmented-block": props.block,
|
||||
"pure-segmented--large": props.size === "large",
|
||||
"pure-segmented--small": props.size === "small"
|
||||
}}
|
||||
>
|
||||
<div class="pure-segmented-group">
|
||||
<div
|
||||
class="pure-segmented-item-selected"
|
||||
style={{
|
||||
width: `${width.value}px`,
|
||||
transform: `translateX(${translateX.value}px)`,
|
||||
display: initStatus.value ? "block" : "none"
|
||||
}}
|
||||
></div>
|
||||
{rendLabel()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,20 +0,0 @@
|
|||
import type { VNode, Component } from "vue";
|
||||
import type { iconType } from "@/components/ReIcon/src/types.ts";
|
||||
|
||||
export interface OptionsType {
|
||||
/** 文字 */
|
||||
label?: string | (() => VNode | Component);
|
||||
/**
|
||||
* @description 图标,采用平台内置的 `useRenderIcon` 函数渲染
|
||||
* @see {@link 用法参考 https://pure-admin.github.io/pure-admin-doc/pages/icon/#%E9%80%9A%E7%94%A8%E5%9B%BE%E6%A0%87-userendericon-hooks }
|
||||
*/
|
||||
icon?: string | Component;
|
||||
/** 图标属性、样式配置 */
|
||||
iconAttrs?: iconType;
|
||||
/** 值 */
|
||||
value?: any;
|
||||
/** 是否禁用 */
|
||||
disabled?: boolean;
|
||||
/** `tooltip` 提示 */
|
||||
tip?: string;
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
import reSegmented from "./src/index";
|
||||
import { withInstall } from "@pureadmin/utils";
|
||||
import reSegmented from './src/index';
|
||||
import { withInstall } from '@pureadmin/utils';
|
||||
|
||||
/** 分段控制器组件 */
|
||||
export const ReSegmented = withInstall(reSegmented);
|
||||
|
||||
export default ReSegmented;
|
||||
export type { OptionsType } from "./src/type";
|
||||
export type { OptionsType } from './src/type';
|
||||
|
|
|
@ -126,13 +126,7 @@ export default defineComponent({
|
|||
onClick={event => handleChange({ option, index }, event)}
|
||||
>
|
||||
<input type='radio' name='segmented' />
|
||||
<div
|
||||
class='pure-segmented-item-label'
|
||||
v-tippy={{
|
||||
content: option?.tip,
|
||||
zIndex: 41000,
|
||||
}}
|
||||
>
|
||||
<div class='pure-segmented-item-label' v-tippy={{ content: option?.tip, zIndex: 41000 }}>
|
||||
{option.icon && !isFunction(option.label) ? (
|
||||
<span class='pure-segmented-item-icon' style={{ marginRight: option.label ? '6px' : 0 }}>
|
||||
{h(
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import type { Component, VNode } from "vue";
|
||||
import type { iconType } from "@/components/CommonIcon/src/types.ts";
|
||||
import type { Component, VNode } from 'vue';
|
||||
import type { iconType } from '@/components/CommonIcon/src/types.ts';
|
||||
|
||||
export interface OptionsType {
|
||||
/** 文字 */
|
||||
label?: string | (() => VNode | Component);
|
||||
/**
|
||||
* @description 图标,采用平台内置的 `useRenderIcon` 函数渲染
|
||||
* @see {@link 用法参考 https://pure-admin.github.io/pure-admin-doc/pages/icon/#%E9%80%9A%E7%94%A8%E5%9B%BE%E6%A0%87-userendericon-hooks }
|
||||
*/
|
||||
icon?: string | Component;
|
||||
/** 图标属性、样式配置 */
|
||||
iconAttrs?: iconType;
|
||||
/** 值 */
|
||||
value?: any;
|
||||
/** 是否禁用 */
|
||||
disabled?: boolean;
|
||||
/** `tooltip` 提示 */
|
||||
tip?: string;
|
||||
/** 文字 */
|
||||
label?: string | (() => VNode | Component);
|
||||
/**
|
||||
* @description 图标,采用平台内置的 `useRenderIcon` 函数渲染
|
||||
* @see {@link 用法参考 https://pure-admin.github.io/pure-admin-doc/pages/icon/#%E9%80%9A%E7%94%A8%E5%9B%BE%E6%A0%87-userendericon-hooks }
|
||||
*/
|
||||
icon?: string | Component;
|
||||
/** 图标属性、样式配置 */
|
||||
iconAttrs?: iconType;
|
||||
/** 值 */
|
||||
value?: any;
|
||||
/** 是否禁用 */
|
||||
disabled?: boolean;
|
||||
/** `tooltip` 提示 */
|
||||
tip?: string;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
<script lang="ts" setup>
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { fetchGetMenuIconList } from '@/api/v1/menuIcon';
|
||||
import { FormProps } from '@/components/SelectIcon/types';
|
||||
import { $t } from '@/plugins/i18n';
|
||||
|
||||
const props = withDefaults(defineProps<FormProps>(), {
|
||||
formInline: () => ({
|
||||
icon: '',
|
||||
}),
|
||||
});
|
||||
|
||||
const innerForm = reactive({
|
||||
datalist: [],
|
||||
currentPage: 1,
|
||||
pageSize: 30,
|
||||
total: 100,
|
||||
loading: false,
|
||||
});
|
||||
const form = ref(props.formInline);
|
||||
|
||||
/**
|
||||
* * 搜索和初始化
|
||||
*/
|
||||
const onSearch = async () => {
|
||||
innerForm.loading = true;
|
||||
|
||||
// 获取数据
|
||||
const baseResult = await fetchGetMenuIconList(innerForm);
|
||||
const data = baseResult.data;
|
||||
|
||||
// 赋值内容
|
||||
innerForm.datalist = data.list;
|
||||
innerForm.currentPage = data.pageNo;
|
||||
innerForm.pageSize = data.pageSize;
|
||||
innerForm.total = data.total;
|
||||
|
||||
innerForm.value = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* * 修改图标
|
||||
* @param value
|
||||
*/
|
||||
const onChangeIcon = (value: any) => {
|
||||
form.value.icon = value.iconName;
|
||||
};
|
||||
|
||||
/**
|
||||
* * 清除图标
|
||||
*/
|
||||
const onClear = () => {
|
||||
form.value.icon = '';
|
||||
};
|
||||
|
||||
/**
|
||||
* * 修改当前页
|
||||
* @param value
|
||||
*/
|
||||
const onCurrentChange = (value: string) => {
|
||||
innerForm.currentPage = value;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
onSearch();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="selector">
|
||||
<el-popover :popper-options="{ placement: 'auto' }" :width="350" popper-class="pure-popper" trigger="click">
|
||||
<template #reference>
|
||||
<div class="w-[60px] h-[32px] cursor-pointer flex justify-center items-center">
|
||||
<el-text v-if="!form.icon" type="primary">{{ $t('select_icon') }}</el-text>
|
||||
<IconifyIconOnline v-else :icon="form.icon" style="font-size: 32px" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<ul class="flex flex-wrap px-2 ml-2">
|
||||
<li
|
||||
v-for="(item, key) in innerForm.datalist"
|
||||
:key="key"
|
||||
:title="item.iconName"
|
||||
class="icon-item p-2 cursor-pointer mr-2 mt-1 flex justify-center items-center border border-[#e5e7eb]"
|
||||
@click="onChangeIcon(item)"
|
||||
>
|
||||
<IconifyIconOnline :icon="item.iconName" height="20px" width="20px" />
|
||||
</li>
|
||||
</ul>
|
||||
<el-empty v-show="innerForm.datalist.length === 0" :image-size="60" description="图标不存在" />
|
||||
|
||||
<div class="w-full h-9 flex items-center overflow-auto border-t border-[#e5e7eb]">
|
||||
<el-pagination
|
||||
:current-page="innerForm.currentPage"
|
||||
:page-size="innerForm.pageSize"
|
||||
:pager-count="5"
|
||||
:total="innerForm.total"
|
||||
background
|
||||
class="flex-auto ml-2"
|
||||
hide-on-single-page
|
||||
layout="pager"
|
||||
size="small"
|
||||
@current-change="onCurrentChange"
|
||||
/>
|
||||
<el-button bg class="justify-end mr-2 ml-2" size="small" text type="danger" @click="onClear"> 清空</el-button>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icon-item {
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
transition: all 0.4s;
|
||||
transform: scaleX(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav-next) {
|
||||
font-size: 15px;
|
||||
line-height: 32px;
|
||||
box-shadow: -5px 0 5px -6px #ccc;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav-prev) {
|
||||
font-size: 15px;
|
||||
line-height: 32px;
|
||||
box-shadow: 5px 0 5px -6px #ccc;
|
||||
}
|
||||
|
||||
:deep(.el-input-group__append) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item) {
|
||||
height: 30px;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__header),
|
||||
:deep(.el-tabs__nav-wrap) {
|
||||
position: static;
|
||||
margin: 0;
|
||||
box-shadow: 0 2px 5px rgb(0 0 0 / 6%);
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav-wrap::after) {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav-wrap) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__content) {
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,8 @@
|
|||
export interface FormItemProps {
|
||||
/** 菜单类型(0代表菜单、1代表iframe、2代表外链、3代表按钮)*/
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export interface FormProps {
|
||||
formInline: FormItemProps;
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
<template>
|
||||
<div class="main mt-2 p-2 bg-bg_color">
|
||||
<pure-table
|
||||
ref="tableRef"
|
||||
:adaptiveConfig="{ offsetBottom: 108 }"
|
||||
:columns="column"
|
||||
:data="dataList"
|
||||
:header-cell-style="cellHeaderStyle"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:paginationSmall="size === 'small'"
|
||||
:size="size"
|
||||
adaptive
|
||||
align-whole="center"
|
||||
border
|
||||
row-key="id"
|
||||
table-layout="auto"
|
||||
@selection-change="handleSelectionChange"
|
||||
@page-size-change="handleSizeChange"
|
||||
@page-current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { cellHeaderStyle } from '@/components/TableBar/utils/tableStyle';
|
||||
import PureTable from '@pureadmin/table';
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
// * 传入数据
|
||||
defineProps({
|
||||
// 表格数据
|
||||
dataList: {
|
||||
type: Array<any>,
|
||||
default: [],
|
||||
},
|
||||
// 表格列字段
|
||||
column: {
|
||||
type: Array<any>,
|
||||
default: [],
|
||||
},
|
||||
// 是否加载
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 页面字体大小,small | default | large
|
||||
size: {
|
||||
type: String as PropType<any>,
|
||||
default: 'default',
|
||||
},
|
||||
// 分页器参数
|
||||
pagination: {
|
||||
type: Object,
|
||||
default: Object,
|
||||
},
|
||||
// 一页大小变化
|
||||
handleSelectionChange: {
|
||||
type: Function as PropType<Function>,
|
||||
default: () => {},
|
||||
},
|
||||
// 分页大小变化
|
||||
handleSizeChange: {
|
||||
type: Function as PropType<Function>,
|
||||
default: () => {},
|
||||
},
|
||||
// 当前页变化
|
||||
handleCurrentChange: {
|
||||
type: Function as PropType<Function>,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
|
@ -1,100 +0,0 @@
|
|||
<template>
|
||||
<div class="main">
|
||||
<el-form ref="formRef" :inline="true" :model="form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
|
||||
<slot name="tableForm" />
|
||||
</el-form>
|
||||
|
||||
<div class="mt-2 p-2 bg-bg_color">
|
||||
<pure-table
|
||||
ref="tableRef"
|
||||
:adaptiveConfig="{ offsetBottom: 108 }"
|
||||
:columns="column"
|
||||
:data="dataList"
|
||||
:header-cell-style="cellHeaderStyle"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
:paginationSmall="size === 'small'"
|
||||
:size="size"
|
||||
adaptive
|
||||
align-whole="center"
|
||||
border
|
||||
row-key="id"
|
||||
table-layout="auto"
|
||||
@selection-change="handleSelectionChange"
|
||||
@page-size-change="handleSizeChange"
|
||||
@page-current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from 'vue';
|
||||
import { cellHeaderStyle } from '@/components/TableBar/utils/tableStyle';
|
||||
import PureTable from '@pureadmin/table';
|
||||
|
||||
// * 传入数据
|
||||
defineProps({
|
||||
// 表格数据
|
||||
dataList: {
|
||||
type: Array<any>,
|
||||
default: [],
|
||||
},
|
||||
// 表格列字段
|
||||
column: {
|
||||
type: Array<any>,
|
||||
default: [],
|
||||
},
|
||||
// 是否加载
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// 页面字体大小,small | default | large
|
||||
size: {
|
||||
type: String as PropType<any>,
|
||||
default: 'default',
|
||||
},
|
||||
// 分页器参数
|
||||
pagination: {
|
||||
type: Object,
|
||||
default: Object,
|
||||
},
|
||||
// 一页大小变化
|
||||
handleSelectionChange: {
|
||||
type: Function as PropType<Function>,
|
||||
default: () => {},
|
||||
},
|
||||
// 分页大小变化
|
||||
handleSizeChange: {
|
||||
type: Function as PropType<Function>,
|
||||
default: () => {},
|
||||
},
|
||||
// 当前页变化
|
||||
handleCurrentChange: {
|
||||
type: Function as PropType<Function>,
|
||||
default: () => {},
|
||||
},
|
||||
// 表单参数
|
||||
form: {
|
||||
type: Object as PropType<any>,
|
||||
default: Object,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-dropdown-menu__item i) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin: 24px 24px 0 !important;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,20 +1,20 @@
|
|||
<script lang="ts" setup>
|
||||
import { h, onMounted, ref, useSlots } from "vue";
|
||||
import { type TippyOptions, useTippy } from "vue-tippy";
|
||||
import { h, onMounted, ref, useSlots } from 'vue';
|
||||
import { type TippyOptions, useTippy } from 'vue-tippy';
|
||||
|
||||
defineOptions({
|
||||
name: "ReText"
|
||||
name: 'ReText',
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
// 行数
|
||||
lineClamp: {
|
||||
type: [String, Number]
|
||||
},
|
||||
tippyProps: {
|
||||
type: Object as PropType<TippyOptions>,
|
||||
default: () => ({})
|
||||
}
|
||||
// 行数
|
||||
lineClamp: {
|
||||
type: [String, Number],
|
||||
},
|
||||
tippyProps: {
|
||||
type: Object as PropType<TippyOptions>,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const $slots = useSlots();
|
||||
|
@ -23,44 +23,44 @@ const textRef = ref();
|
|||
const tippyFunc = ref();
|
||||
|
||||
const isTextEllipsis = (el: HTMLElement) => {
|
||||
if (!props.lineClamp) {
|
||||
// 单行省略判断
|
||||
return el.scrollWidth > el.clientWidth;
|
||||
} else {
|
||||
// 多行省略判断
|
||||
return el.scrollHeight > el.clientHeight;
|
||||
}
|
||||
if (!props.lineClamp) {
|
||||
// 单行省略判断
|
||||
return el.scrollWidth > el.clientWidth;
|
||||
} else {
|
||||
// 多行省略判断
|
||||
return el.scrollHeight > el.clientHeight;
|
||||
}
|
||||
};
|
||||
|
||||
const getTippyProps = () => ({
|
||||
content: h($slots.content || $slots.default),
|
||||
...props.tippyProps
|
||||
content: h($slots.content || $slots.default),
|
||||
...props.tippyProps,
|
||||
});
|
||||
|
||||
function handleHover(event: MouseEvent) {
|
||||
if (isTextEllipsis(event.target as HTMLElement)) {
|
||||
tippyFunc.value.setProps(getTippyProps());
|
||||
tippyFunc.value.enable();
|
||||
} else {
|
||||
tippyFunc.value.disable();
|
||||
}
|
||||
if (isTextEllipsis(event.target as HTMLElement)) {
|
||||
tippyFunc.value.setProps(getTippyProps());
|
||||
tippyFunc.value.enable();
|
||||
} else {
|
||||
tippyFunc.value.disable();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
tippyFunc.value = useTippy(textRef.value?.$el, getTippyProps());
|
||||
tippyFunc.value = useTippy(textRef.value?.$el, getTippyProps());
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-text
|
||||
ref="textRef"
|
||||
v-bind="{
|
||||
truncated: !lineClamp,
|
||||
lineClamp,
|
||||
...$attrs
|
||||
}"
|
||||
@mouseover.self="handleHover"
|
||||
>
|
||||
<slot />
|
||||
</el-text>
|
||||
<el-text
|
||||
ref="textRef"
|
||||
v-bind="{
|
||||
truncated: !lineClamp,
|
||||
lineClamp,
|
||||
...$attrs,
|
||||
}"
|
||||
@mouseover.self="handleHover"
|
||||
>
|
||||
<slot />
|
||||
</el-text>
|
||||
</template>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { OptionsType } from '@/components/ReSegmented';
|
||||
import type { OptionsType } from '@/components/Segmented';
|
||||
|
||||
export const menuTypeOptions: Array<OptionsType> = [
|
||||
{ label: '菜单', value: 0 },
|
||||
|
@ -8,11 +8,7 @@ export const menuTypeOptions: Array<OptionsType> = [
|
|||
|
||||
export const showLinkOptions: Array<OptionsType> = [
|
||||
{ label: '显示', tip: '会在菜单中显示', value: true },
|
||||
{
|
||||
label: '隐藏',
|
||||
tip: '不会在菜单中显示',
|
||||
value: false,
|
||||
},
|
||||
{ label: '隐藏', tip: '不会在菜单中显示', value: false },
|
||||
];
|
||||
|
||||
export const fixedTagOptions: Array<OptionsType> = [
|
||||
|
|
|
@ -2,6 +2,7 @@ import { defineStore } from 'pinia';
|
|||
import { fetchAddI18n, fetchDeleteI18n, fetchGetI18n, fetchGetI18nList, fetchUpdateI18n } from '@/api/v1/i18n';
|
||||
import { pageSizes } from '@/enums/baseConstant';
|
||||
import { storeMessage } from '@/utils/message';
|
||||
import { storePagination } from '@/store/useStorePagination';
|
||||
|
||||
export const userI18nStore = defineStore('i18nStore', {
|
||||
persist: true,
|
||||
|
@ -55,16 +56,9 @@ export const userI18nStore = defineStore('i18nStore', {
|
|||
delete data.background;
|
||||
const result = await fetchGetI18nList(data);
|
||||
|
||||
// 如果成功赋值内容
|
||||
if (result.code === 200) {
|
||||
this.datalist = result.data.list;
|
||||
this.pagination.currentPage = result.data.pageNo;
|
||||
this.pagination.pageSize = result.data.pageSize;
|
||||
this.pagination.total = result.data.total;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
// 公共页面函数hook
|
||||
const pagination = storePagination.bind(this);
|
||||
return pagination(result);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,7 +4,6 @@ import { pageSizes } from '@/enums/baseConstant';
|
|||
import { storeMessage } from '@/utils/message';
|
||||
|
||||
export const userI18nTypeStore = defineStore('i18nTypeStore', {
|
||||
persist: true,
|
||||
state() {
|
||||
return {
|
||||
// 多语言列表
|
||||
|
|
|
@ -1,23 +1,72 @@
|
|||
import { defineStore } from 'pinia';
|
||||
import { getMenuIconList } from '@/api/v1/system';
|
||||
import { fetchAddMenuIcon, fetchDeleteMenuIcon, fetchGetMenuIconList, fetchUpdateMenuIcon } from '@/api/v1/menuIcon';
|
||||
import { pageSizes } from '@/enums/baseConstant';
|
||||
import { storeMessage } from '@/utils/message';
|
||||
import { storePagination } from '@/store/useStorePagination';
|
||||
|
||||
export const userMenuIconStore = defineStore('menuIconStore', {
|
||||
/**
|
||||
* 系统菜单图标 Store
|
||||
*/
|
||||
export const useMenuIconStore = defineStore('menuIconStore', {
|
||||
state() {
|
||||
return {
|
||||
menuIconList: [],
|
||||
menuIconPage: { page: 1, limit: 30, total: 0 },
|
||||
// 系统菜单图标列表
|
||||
datalist: [],
|
||||
// 查询表单
|
||||
form: {
|
||||
// icon 名称
|
||||
iconName: undefined,
|
||||
},
|
||||
// 分页查询结果
|
||||
pagination: {
|
||||
currentPage: 1,
|
||||
pageSize: 150,
|
||||
total: 100,
|
||||
pageSizes,
|
||||
},
|
||||
// 加载
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
getters: {},
|
||||
actions: {
|
||||
/**
|
||||
* * 获取系统图标列表
|
||||
* * 获取系统菜单图标
|
||||
*/
|
||||
async getMenuIconList() {
|
||||
const result = await getMenuIconList(this.menuIconPage);
|
||||
if (result.code === 200) {
|
||||
this.menuIconList = result.data.list;
|
||||
}
|
||||
const data = { ...this.pagination, ...this.form };
|
||||
delete data.pageSizes;
|
||||
delete data.total;
|
||||
delete data.background;
|
||||
const response = await fetchGetMenuIconList(data);
|
||||
|
||||
// 公共页面函数hook
|
||||
const pagination = storePagination.bind(this);
|
||||
return pagination(response);
|
||||
},
|
||||
|
||||
/**
|
||||
* * 添加系统菜单图标
|
||||
*/
|
||||
async addMenuIcon(data: any) {
|
||||
const result = await fetchAddMenuIcon(data);
|
||||
return storeMessage(result);
|
||||
},
|
||||
|
||||
/**
|
||||
* * 修改系统菜单图标
|
||||
*/
|
||||
async updateMenuIcon(data: any) {
|
||||
const result = await fetchUpdateMenuIcon(data);
|
||||
return storeMessage(result);
|
||||
},
|
||||
|
||||
/**
|
||||
* * 删除系统菜单图标
|
||||
*/
|
||||
async deleteMenuIcon(data: any) {
|
||||
const result = await fetchDeleteMenuIcon(data);
|
||||
return storeMessage(result);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { defineStore } from 'pinia';
|
||||
import { addMenu, deletedMenuByIds, getMenuList, updateMenu } from '@/api/v1/system';
|
||||
import { addMenu, deletedMenuByIds, getMenusList, updateMenu } from '@/api/v1/system';
|
||||
import { storeMessage } from '@/utils/message';
|
||||
import { handleTree } from '@/utils/tree';
|
||||
|
||||
|
@ -7,6 +7,7 @@ export const userRouterStore = defineStore('routerStore', {
|
|||
state() {
|
||||
return {
|
||||
datalist: [],
|
||||
form: { title: undefined, visible: undefined },
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
|
@ -16,7 +17,9 @@ export const userRouterStore = defineStore('routerStore', {
|
|||
* * 获取菜单列表
|
||||
*/
|
||||
async getMenuList() {
|
||||
const result = await getMenuList();
|
||||
const data = { ...this.pagination, ...this.form };
|
||||
const result = await getMenusList(data);
|
||||
|
||||
if (result.code === 200) {
|
||||
this.datalist = handleTree(result.data as any);
|
||||
return true;
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* * 通用表单分页
|
||||
* @param response
|
||||
*/
|
||||
export function storePagination(response: any) {
|
||||
// 返回成功为其赋值
|
||||
if (response.code === 200) {
|
||||
this.datalist = response.data.list;
|
||||
this.pagination.currentPage = response.data.pageNo;
|
||||
this.pagination.pageSize = response.data.pageSize;
|
||||
this.pagination.total = response.data.total;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import ReCol from "@/components/MyCol";
|
||||
import { formRules } from "./utils/rule";
|
||||
import { FormProps } from "./utils/types";
|
||||
import { usePublicHooks } from "../hooks";
|
||||
|
||||
const props = withDefaults(defineProps<FormProps>(), {
|
||||
formInline: () => ({
|
||||
higherDeptOptions: [],
|
||||
parentId: 0,
|
||||
name: "",
|
||||
principal: "",
|
||||
phone: "",
|
||||
email: "",
|
||||
sort: 0,
|
||||
status: 1,
|
||||
remark: ""
|
||||
})
|
||||
});
|
||||
|
||||
const ruleFormRef = ref();
|
||||
const { switchStyle } = usePublicHooks();
|
||||
const newFormInline = ref(props.formInline);
|
||||
|
||||
function getRef() {
|
||||
return ruleFormRef.value;
|
||||
}
|
||||
|
||||
defineExpose({ getRef });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form
|
||||
ref="ruleFormRef"
|
||||
:model="newFormInline"
|
||||
:rules="formRules"
|
||||
label-width="82px"
|
||||
>
|
||||
<el-row :gutter="30">
|
||||
<re-col>
|
||||
<el-form-item label="上级部门">
|
||||
<el-cascader
|
||||
v-model="newFormInline.parentId"
|
||||
:options="newFormInline.higherDeptOptions"
|
||||
:props="{
|
||||
value: 'id',
|
||||
label: 'name',
|
||||
emitPath: false,
|
||||
checkStrictly: true
|
||||
}"
|
||||
class="w-full"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择上级部门"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<span>{{ data.name }}</span>
|
||||
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
|
||||
</template>
|
||||
</el-cascader>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="部门名称" prop="name">
|
||||
<el-input
|
||||
v-model="newFormInline.name"
|
||||
clearable
|
||||
placeholder="请输入部门名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="部门负责人">
|
||||
<el-input
|
||||
v-model="newFormInline.principal"
|
||||
clearable
|
||||
placeholder="请输入部门负责人"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input
|
||||
v-model="newFormInline.phone"
|
||||
clearable
|
||||
placeholder="请输入手机号"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input
|
||||
v-model="newFormInline.email"
|
||||
clearable
|
||||
placeholder="请输入邮箱"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="排序">
|
||||
<el-input-number
|
||||
v-model="newFormInline.sort"
|
||||
:max="9999"
|
||||
:min="0"
|
||||
class="!w-full"
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="部门状态">
|
||||
<el-switch
|
||||
v-model="newFormInline.status"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
:style="switchStyle"
|
||||
active-text="启用"
|
||||
inactive-text="停用"
|
||||
inline-prompt
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col>
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="newFormInline.remark"
|
||||
placeholder="请输入备注信息"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
|
@ -1,172 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import { useDept } from "./utils/hook";
|
||||
import { PureTableBar } from "@/components/TableBar";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
|
||||
import Delete from "@iconify-icons/ep/delete";
|
||||
import EditPen from "@iconify-icons/ep/edit-pen";
|
||||
import Refresh from "@iconify-icons/ep/refresh";
|
||||
import AddFill from "@iconify-icons/ri/add-circle-line";
|
||||
|
||||
defineOptions({
|
||||
name: "SystemDept"
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
const tableRef = ref();
|
||||
const {
|
||||
form,
|
||||
loading,
|
||||
columns,
|
||||
dataList,
|
||||
onSearch,
|
||||
resetForm,
|
||||
openDialog,
|
||||
handleDelete,
|
||||
handleSelectionChange
|
||||
} = useDept();
|
||||
|
||||
function onFullscreen() {
|
||||
// 重置表格高度
|
||||
tableRef.value.setAdaptive();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:inline="true"
|
||||
:model="form"
|
||||
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto"
|
||||
>
|
||||
<el-form-item label="部门名称:" prop="name">
|
||||
<el-input
|
||||
v-model="form.name"
|
||||
class="!w-[180px]"
|
||||
clearable
|
||||
placeholder="请输入部门名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:" prop="status">
|
||||
<el-select
|
||||
v-model="form.status"
|
||||
class="!w-[180px]"
|
||||
clearable
|
||||
placeholder="请选择状态"
|
||||
>
|
||||
<el-option :value="1" label="启用" />
|
||||
<el-option :value="0" label="停用" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
:icon="useRenderIcon('ri:search-line')"
|
||||
:loading="loading"
|
||||
type="primary"
|
||||
@click="onSearch"
|
||||
>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<PureTableBar
|
||||
:columns="columns"
|
||||
:tableRef="tableRef?.getTableRef()"
|
||||
title="部门管理(仅演示,操作后不生效)"
|
||||
@fullscreen="onFullscreen"
|
||||
@refresh="onSearch"
|
||||
>
|
||||
<template #buttons>
|
||||
<el-button
|
||||
:icon="useRenderIcon(AddFill)"
|
||||
type="primary"
|
||||
@click="openDialog()"
|
||||
>
|
||||
新增部门
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-slot="{ size, dynamicColumns }">
|
||||
<pure-table
|
||||
ref="tableRef"
|
||||
:adaptiveConfig="{ offsetBottom: 45 }"
|
||||
:columns="dynamicColumns"
|
||||
:data="dataList"
|
||||
:header-cell-style="{
|
||||
background: 'var(--el-fill-color-light)',
|
||||
color: 'var(--el-text-color-primary)'
|
||||
}"
|
||||
:loading="loading"
|
||||
:size="size"
|
||||
adaptive
|
||||
align-whole="center"
|
||||
default-expand-all
|
||||
row-key="id"
|
||||
showOverflowTooltip
|
||||
table-layout="auto"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<template #operation="{ row }">
|
||||
<el-button
|
||||
:icon="useRenderIcon(EditPen)"
|
||||
:size="size"
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
@click="openDialog('修改', row)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-button
|
||||
:icon="useRenderIcon(AddFill)"
|
||||
:size="size"
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
@click="openDialog('新增', { parentId: row.id } as any)"
|
||||
>
|
||||
新增
|
||||
</el-button>
|
||||
<el-popconfirm
|
||||
:title="`是否确认删除部门名称为${row.name}的这条数据`"
|
||||
@confirm="handleDelete(row)"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button
|
||||
:icon="useRenderIcon(Delete)"
|
||||
:size="size"
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</pure-table>
|
||||
</template>
|
||||
</PureTableBar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-table__inner-wrapper::before) {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin: 24px 24px 0 !important;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,180 +0,0 @@
|
|||
import dayjs from "dayjs";
|
||||
import editForm from "../form.vue";
|
||||
import { handleTree } from "@/utils/tree";
|
||||
import { message } from "@/utils/message";
|
||||
import { getDeptList } from "@/api/v1/system";
|
||||
import { usePublicHooks } from "../../hooks";
|
||||
import { addDialog } from "@/components/BaseDialog";
|
||||
import { h, onMounted, reactive, ref } from "vue";
|
||||
import type { FormItemProps } from "../utils/types";
|
||||
import { cloneDeep, deviceDetection, isAllEmpty } from "@pureadmin/utils";
|
||||
|
||||
export function useDept() {
|
||||
const form = reactive({
|
||||
name: "",
|
||||
status: null
|
||||
});
|
||||
|
||||
const formRef = ref();
|
||||
const dataList = ref([]);
|
||||
const loading = ref(true);
|
||||
const { tagStyle } = usePublicHooks();
|
||||
|
||||
const columns: TableColumnList = [
|
||||
{
|
||||
label: "部门名称",
|
||||
prop: "name",
|
||||
width: 180,
|
||||
align: "left"
|
||||
},
|
||||
{
|
||||
label: "排序",
|
||||
prop: "sort",
|
||||
minWidth: 70
|
||||
},
|
||||
{
|
||||
label: "状态",
|
||||
prop: "status",
|
||||
minWidth: 100,
|
||||
cellRenderer: ({ row, props }) => (
|
||||
<el-tag size={props.size} style={tagStyle.value(row.status)}>
|
||||
{row.status === 1 ? "启用" : "停用"}
|
||||
</el-tag>
|
||||
)
|
||||
},
|
||||
{
|
||||
label: "创建时间",
|
||||
minWidth: 200,
|
||||
prop: "createTime",
|
||||
formatter: ({ createTime }) =>
|
||||
dayjs(createTime).format("YYYY-MM-DD HH:mm:ss")
|
||||
},
|
||||
{
|
||||
label: "备注",
|
||||
prop: "remark",
|
||||
minWidth: 320
|
||||
},
|
||||
{
|
||||
label: "操作",
|
||||
fixed: "right",
|
||||
width: 210,
|
||||
slot: "operation"
|
||||
}
|
||||
];
|
||||
|
||||
function handleSelectionChange(val) {
|
||||
console.log("handleSelectionChange", val);
|
||||
}
|
||||
|
||||
function resetForm(formEl) {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
onSearch();
|
||||
}
|
||||
|
||||
async function onSearch() {
|
||||
loading.value = true;
|
||||
const { data } = await getDeptList(); // 这里是返回一维数组结构,前端自行处理成树结构,返回格式要求:唯一id加父节点parentId,parentId取父节点id
|
||||
let newData = data;
|
||||
if (!isAllEmpty(form.name)) {
|
||||
// 前端搜索部门名称
|
||||
newData = newData.filter(item => item.name.includes(form.name));
|
||||
}
|
||||
if (!isAllEmpty(form.status)) {
|
||||
// 前端搜索状态
|
||||
newData = newData.filter(item => item.status === form.status);
|
||||
}
|
||||
dataList.value = handleTree(newData); // 处理成树结构
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function formatHigherDeptOptions(treeList) {
|
||||
// 根据返回数据的status字段值判断追加是否禁用disabled字段,返回处理后的树结构,用于上级部门级联选择器的展示(实际开发中也是如此,不可能前端需要的每个字段后端都会返回,这时需要前端自行根据后端返回的某些字段做逻辑处理)
|
||||
if (!treeList || !treeList.length) return;
|
||||
const newTreeList = [];
|
||||
for (let i = 0; i < treeList.length; i++) {
|
||||
treeList[i].disabled = treeList[i].status === 0 ? true : false;
|
||||
formatHigherDeptOptions(treeList[i].children);
|
||||
newTreeList.push(treeList[i]);
|
||||
}
|
||||
return newTreeList;
|
||||
}
|
||||
|
||||
function openDialog(title = "新增", row?: FormItemProps) {
|
||||
addDialog({
|
||||
title: `${title}部门`,
|
||||
props: {
|
||||
formInline: {
|
||||
higherDeptOptions: formatHigherDeptOptions(cloneDeep(dataList.value)),
|
||||
parentId: row?.parentId ?? 0,
|
||||
name: row?.name ?? "",
|
||||
principal: row?.principal ?? "",
|
||||
phone: row?.phone ?? "",
|
||||
email: row?.email ?? "",
|
||||
sort: row?.sort ?? 0,
|
||||
status: row?.status ?? 1,
|
||||
remark: row?.remark ?? ""
|
||||
}
|
||||
},
|
||||
width: "40%",
|
||||
draggable: true,
|
||||
fullscreen: deviceDetection(),
|
||||
fullscreenIcon: true,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () => h(editForm, { ref: formRef, formInline: null }),
|
||||
beforeSure: (done, { options }) => {
|
||||
const FormRef = formRef.value.getRef();
|
||||
const curData = options.props.formInline as FormItemProps;
|
||||
|
||||
function chores() {
|
||||
message(`您${title}了部门名称为${curData.name}的这条数据`, {
|
||||
type: "success"
|
||||
});
|
||||
done(); // 关闭弹框
|
||||
onSearch(); // 刷新表格数据
|
||||
}
|
||||
|
||||
FormRef.validate(valid => {
|
||||
if (valid) {
|
||||
console.log("curData", curData);
|
||||
// 表单规则校验通过
|
||||
if (title === "新增") {
|
||||
// 实际开发先调用新增接口,再进行下面操作
|
||||
chores();
|
||||
} else {
|
||||
// 实际开发先调用修改接口,再进行下面操作
|
||||
chores();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleDelete(row) {
|
||||
message(`您删除了部门名称为${row.name}的这条数据`, { type: "success" });
|
||||
onSearch();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onSearch();
|
||||
});
|
||||
|
||||
return {
|
||||
form,
|
||||
loading,
|
||||
columns,
|
||||
dataList,
|
||||
/** 搜索 */
|
||||
onSearch,
|
||||
/** 重置 */
|
||||
resetForm,
|
||||
/** 新增、修改部门 */
|
||||
openDialog,
|
||||
/** 删除部门 */
|
||||
handleDelete,
|
||||
handleSelectionChange
|
||||
};
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
import { reactive } from "vue";
|
||||
import type { FormRules } from "element-plus";
|
||||
import { isPhone, isEmail } from "@pureadmin/utils";
|
||||
|
||||
/** 自定义表单规则校验 */
|
||||
export const formRules = reactive(<FormRules>{
|
||||
name: [{ required: true, message: "部门名称为必填项", trigger: "blur" }],
|
||||
phone: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback();
|
||||
} else if (!isPhone(value)) {
|
||||
callback(new Error("请输入正确的手机号码格式"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: "blur"
|
||||
// trigger: "click" // 如果想在点击确定按钮时触发这个校验,trigger 设置成 click 即可
|
||||
}
|
||||
],
|
||||
email: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === "") {
|
||||
callback();
|
||||
} else if (!isEmail(value)) {
|
||||
callback(new Error("请输入正确的邮箱格式"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: "blur"
|
||||
}
|
||||
]
|
||||
});
|
|
@ -1,16 +0,0 @@
|
|||
interface FormItemProps {
|
||||
higherDeptOptions: Record<string, unknown>[];
|
||||
parentId: number;
|
||||
name: string;
|
||||
principal: string;
|
||||
phone: string | number;
|
||||
email: string;
|
||||
sort: number;
|
||||
status: number;
|
||||
remark: string;
|
||||
}
|
||||
interface FormProps {
|
||||
formInline: FormItemProps;
|
||||
}
|
||||
|
||||
export type { FormItemProps, FormProps };
|
|
@ -2,7 +2,7 @@
|
|||
import { onMounted, ref } from 'vue';
|
||||
import { userI18nStore } from '@/store/i18n/i18n';
|
||||
import { deleteIds, onAdd, onDelete, onDeleteBatch, onSearch, onUpdate } from '@/views/i18n/i18n-setting/utils/hook';
|
||||
import { useRenderIcon } from '@/components/ReIcon/src/hooks';
|
||||
import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
|
||||
import AddFill from '@iconify-icons/ri/add-circle-line';
|
||||
import EditPen from '@iconify-icons/ep/edit-pen';
|
||||
import Delete from '@iconify-icons/ep/delete';
|
||||
|
|
|
@ -4,7 +4,7 @@ import { FormInstance } from 'element-plus';
|
|||
import { rules } from '@/views/i18n/i18n-type-setting/utils/columns';
|
||||
import { FormProps } from '@/views/i18n/i18n-type-setting/utils/types';
|
||||
import { frameSureOptions } from '@/enums';
|
||||
import Segmented from '@/components/ReSegmented';
|
||||
import Segmented from '@/components/Segmented';
|
||||
|
||||
const props = withDefaults(defineProps<FormProps>(), {
|
||||
formInline: () => ({
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { onMounted, ref } from 'vue';
|
||||
import { columns } from '@/views/i18n/i18n-type-setting/utils/columns';
|
||||
import PureTableBar from '@/components/TableBar/src/bar';
|
||||
import { useRenderIcon } from '@/components/ReIcon/src/hooks';
|
||||
import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
|
||||
import AddFill from '@iconify-icons/ri/add-circle-line';
|
||||
import PureTable from '@pureadmin/table';
|
||||
import { userI18nTypeStore } from '@/store/i18n/i18nType';
|
||||
|
@ -10,7 +10,6 @@ import { onAdd, onDelete, onSearch, onUpdate } from '@/views/i18n/i18n-type-sett
|
|||
import Delete from '@iconify-icons/ep/delete';
|
||||
import EditPen from '@iconify-icons/ep/edit-pen';
|
||||
import TableIsDefaultTag from '@/components/TableBar/src/TableIsDefaultTag.vue';
|
||||
import { resetForm } from '@/views/system/menu/utils/hook';
|
||||
import Refresh from '@iconify-icons/ep/refresh';
|
||||
import { selectUserinfo } from '@/components/Table/Userinfo/columns';
|
||||
import { $t } from '@/plugins/i18n';
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { hasAuth, getAuths } from "@/router/utils";
|
||||
|
||||
defineOptions({
|
||||
name: "PermissionButtonRouter"
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p class="mb-2">当前拥有的code列表:{{ getAuths() }}</p>
|
||||
|
||||
<el-card shadow="never" class="mb-2">
|
||||
<template #header>
|
||||
<div class="card-header">组件方式判断权限</div>
|
||||
</template>
|
||||
<el-space wrap>
|
||||
<Auth value="permission:btn:add">
|
||||
<el-button plain type="warning">
|
||||
拥有code:'permission:btn:add' 权限可见
|
||||
</el-button>
|
||||
</Auth>
|
||||
<Auth :value="['permission:btn:edit']">
|
||||
<el-button plain type="primary">
|
||||
拥有code:['permission:btn:edit'] 权限可见
|
||||
</el-button>
|
||||
</Auth>
|
||||
<Auth
|
||||
:value="[
|
||||
'permission:btn:add',
|
||||
'permission:btn:edit',
|
||||
'permission:btn:delete'
|
||||
]"
|
||||
>
|
||||
<el-button plain type="danger">
|
||||
拥有code:['permission:btn:add', 'permission:btn:edit',
|
||||
'permission:btn:delete'] 权限可见
|
||||
</el-button>
|
||||
</Auth>
|
||||
</el-space>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mb-2">
|
||||
<template #header>
|
||||
<div class="card-header">函数方式判断权限</div>
|
||||
</template>
|
||||
<el-space wrap>
|
||||
<el-button v-if="hasAuth('permission:btn:add')" plain type="warning">
|
||||
拥有code:'permission:btn:add' 权限可见
|
||||
</el-button>
|
||||
<el-button v-if="hasAuth(['permission:btn:edit'])" plain type="primary">
|
||||
拥有code:['permission:btn:edit'] 权限可见
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="
|
||||
hasAuth([
|
||||
'permission:btn:add',
|
||||
'permission:btn:edit',
|
||||
'permission:btn:delete'
|
||||
])
|
||||
"
|
||||
plain
|
||||
type="danger"
|
||||
>
|
||||
拥有code:['permission:btn:add', 'permission:btn:edit',
|
||||
'permission:btn:delete'] 权限可见
|
||||
</el-button>
|
||||
</el-space>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
指令方式判断权限(该方式不能动态修改权限)
|
||||
</div>
|
||||
</template>
|
||||
<el-space wrap>
|
||||
<el-button v-auth="'permission:btn:add'" plain type="warning">
|
||||
拥有code:'permission:btn:add' 权限可见
|
||||
</el-button>
|
||||
<el-button v-auth="['permission:btn:edit']" plain type="primary">
|
||||
拥有code:['permission:btn:edit'] 权限可见
|
||||
</el-button>
|
||||
<el-button
|
||||
v-auth="[
|
||||
'permission:btn:add',
|
||||
'permission:btn:edit',
|
||||
'permission:btn:delete'
|
||||
]"
|
||||
plain
|
||||
type="danger"
|
||||
>
|
||||
拥有code:['permission:btn:add', 'permission:btn:edit',
|
||||
'permission:btn:delete'] 权限可见
|
||||
</el-button>
|
||||
</el-space>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
|
@ -1,109 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { hasPerms } from "@/utils/auth";
|
||||
import { useUserStoreHook } from "@/store/modules/user";
|
||||
|
||||
const { permissions } = useUserStoreHook();
|
||||
|
||||
defineOptions({
|
||||
name: "PermissionButtonLogin"
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p class="mb-2">当前拥有的code列表:{{ permissions }}</p>
|
||||
<p v-show="permissions?.[0] === '*:*:*'" class="mb-2">
|
||||
*:*:* 代表拥有全部按钮级别权限
|
||||
</p>
|
||||
|
||||
<el-card shadow="never" class="mb-2">
|
||||
<template #header>
|
||||
<div class="card-header">组件方式判断权限</div>
|
||||
</template>
|
||||
<el-space wrap>
|
||||
<Perms value="permission:btn:add">
|
||||
<el-button plain type="warning">
|
||||
拥有code:'permission:btn:add' 权限可见
|
||||
</el-button>
|
||||
</Perms>
|
||||
<Perms :value="['permission:btn:edit']">
|
||||
<el-button plain type="primary">
|
||||
拥有code:['permission:btn:edit'] 权限可见
|
||||
</el-button>
|
||||
</Perms>
|
||||
<Perms
|
||||
:value="[
|
||||
'permission:btn:add',
|
||||
'permission:btn:edit',
|
||||
'permission:btn:delete'
|
||||
]"
|
||||
>
|
||||
<el-button plain type="danger">
|
||||
拥有code:['permission:btn:add', 'permission:btn:edit',
|
||||
'permission:btn:delete'] 权限可见
|
||||
</el-button>
|
||||
</Perms>
|
||||
</el-space>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mb-2">
|
||||
<template #header>
|
||||
<div class="card-header">函数方式判断权限</div>
|
||||
</template>
|
||||
<el-space wrap>
|
||||
<el-button v-if="hasPerms('permission:btn:add')" plain type="warning">
|
||||
拥有code:'permission:btn:add' 权限可见
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="hasPerms(['permission:btn:edit'])"
|
||||
plain
|
||||
type="primary"
|
||||
>
|
||||
拥有code:['permission:btn:edit'] 权限可见
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="
|
||||
hasPerms([
|
||||
'permission:btn:add',
|
||||
'permission:btn:edit',
|
||||
'permission:btn:delete'
|
||||
])
|
||||
"
|
||||
plain
|
||||
type="danger"
|
||||
>
|
||||
拥有code:['permission:btn:add', 'permission:btn:edit',
|
||||
'permission:btn:delete'] 权限可见
|
||||
</el-button>
|
||||
</el-space>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
指令方式判断权限(该方式不能动态修改权限)
|
||||
</div>
|
||||
</template>
|
||||
<el-space wrap>
|
||||
<el-button v-perms="'permission:btn:add'" plain type="warning">
|
||||
拥有code:'permission:btn:add' 权限可见
|
||||
</el-button>
|
||||
<el-button v-perms="['permission:btn:edit']" plain type="primary">
|
||||
拥有code:['permission:btn:edit'] 权限可见
|
||||
</el-button>
|
||||
<el-button
|
||||
v-perms="[
|
||||
'permission:btn:add',
|
||||
'permission:btn:edit',
|
||||
'permission:btn:delete'
|
||||
]"
|
||||
plain
|
||||
type="danger"
|
||||
>
|
||||
拥有code:['permission:btn:add', 'permission:btn:edit',
|
||||
'permission:btn:delete'] 权限可见
|
||||
</el-button>
|
||||
</el-space>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
|
@ -1,59 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { initRouter } from '@/router/utils';
|
||||
import { storageLocal } from '@pureadmin/utils';
|
||||
import { computed, type CSSProperties, ref } from 'vue';
|
||||
import { useUserStoreHook } from '@/store/modules/user';
|
||||
import { usePermissionStoreHook } from '@/store/permission';
|
||||
|
||||
defineOptions({
|
||||
name: 'PermissionPage',
|
||||
});
|
||||
|
||||
const elStyle = computed((): CSSProperties => {
|
||||
return {
|
||||
width: '85vw',
|
||||
justifyContent: 'start',
|
||||
};
|
||||
});
|
||||
|
||||
const username = ref(useUserStoreHook()?.username);
|
||||
|
||||
const options = [
|
||||
{
|
||||
value: 'admin',
|
||||
label: '管理员角色',
|
||||
},
|
||||
{
|
||||
value: 'common',
|
||||
label: '普通角色',
|
||||
},
|
||||
];
|
||||
|
||||
function onChange() {
|
||||
useUserStoreHook()
|
||||
.loginByUsername({ username: username.value, password: 'admin123' })
|
||||
.then(res => {
|
||||
if (res.success) {
|
||||
storageLocal().removeItem('async-routes');
|
||||
usePermissionStoreHook().clearAllCachePage();
|
||||
initRouter();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p class="mb-2">模拟后台根据不同角色返回对应路由,观察左侧菜单变化(管理员角色可查看系统管理菜单、普通角色不可查看系统管理菜单)</p>
|
||||
<el-card :style="elStyle" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>当前角色:{{ username }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-select v-model="username" class="!w-[160px]" @change="onChange">
|
||||
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
|
@ -1,55 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { formRules } from "./utils/rule";
|
||||
import { FormProps } from "./utils/types";
|
||||
|
||||
const props = withDefaults(defineProps<FormProps>(), {
|
||||
formInline: () => ({
|
||||
name: "",
|
||||
code: "",
|
||||
remark: ""
|
||||
})
|
||||
});
|
||||
|
||||
const ruleFormRef = ref();
|
||||
const newFormInline = ref(props.formInline);
|
||||
|
||||
function getRef() {
|
||||
return ruleFormRef.value;
|
||||
}
|
||||
|
||||
defineExpose({ getRef });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form
|
||||
ref="ruleFormRef"
|
||||
:model="newFormInline"
|
||||
:rules="formRules"
|
||||
label-width="82px"
|
||||
>
|
||||
<el-form-item label="角色名称" prop="name">
|
||||
<el-input
|
||||
v-model="newFormInline.name"
|
||||
clearable
|
||||
placeholder="请输入角色名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="角色标识" prop="code">
|
||||
<el-input
|
||||
v-model="newFormInline.code"
|
||||
clearable
|
||||
placeholder="请输入角色标识"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="newFormInline.remark"
|
||||
placeholder="请输入备注信息"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
|
@ -1,344 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { useRole } from "./utils/hook";
|
||||
import { ref, computed, nextTick, onMounted } from "vue";
|
||||
import { PureTableBar } from "@/components/RePureTableBar";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import {
|
||||
delay,
|
||||
subBefore,
|
||||
deviceDetection,
|
||||
useResizeObserver
|
||||
} from "@pureadmin/utils";
|
||||
|
||||
// import Database from "@iconify-icons/ri/database-2-line";
|
||||
// import More from "@iconify-icons/ep/more-filled";
|
||||
import Delete from "@iconify-icons/ep/delete";
|
||||
import EditPen from "@iconify-icons/ep/edit-pen";
|
||||
import Refresh from "@iconify-icons/ep/refresh";
|
||||
import Menu from "@iconify-icons/ep/menu";
|
||||
import AddFill from "@iconify-icons/ri/add-circle-line";
|
||||
import Close from "@iconify-icons/ep/close";
|
||||
import Check from "@iconify-icons/ep/check";
|
||||
|
||||
defineOptions({
|
||||
name: "SystemRole"
|
||||
});
|
||||
|
||||
const iconClass = computed(() => {
|
||||
return [
|
||||
"w-[22px]",
|
||||
"h-[22px]",
|
||||
"flex",
|
||||
"justify-center",
|
||||
"items-center",
|
||||
"outline-none",
|
||||
"rounded-[4px]",
|
||||
"cursor-pointer",
|
||||
"transition-colors",
|
||||
"hover:bg-[#0000000f]",
|
||||
"dark:hover:bg-[#ffffff1f]",
|
||||
"dark:hover:text-[#ffffffd9]"
|
||||
];
|
||||
});
|
||||
|
||||
const treeRef = ref();
|
||||
const formRef = ref();
|
||||
const tableRef = ref();
|
||||
const contentRef = ref();
|
||||
const treeHeight = ref();
|
||||
|
||||
const {
|
||||
form,
|
||||
isShow,
|
||||
curRow,
|
||||
loading,
|
||||
columns,
|
||||
rowStyle,
|
||||
dataList,
|
||||
treeData,
|
||||
treeProps,
|
||||
isLinkage,
|
||||
pagination,
|
||||
isExpandAll,
|
||||
isSelectAll,
|
||||
treeSearchValue,
|
||||
// buttonClass,
|
||||
onSearch,
|
||||
resetForm,
|
||||
openDialog,
|
||||
handleMenu,
|
||||
handleSave,
|
||||
handleDelete,
|
||||
filterMethod,
|
||||
transformI18n,
|
||||
onQueryChanged,
|
||||
// handleDatabase,
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
handleSelectionChange
|
||||
} = useRole(treeRef);
|
||||
|
||||
onMounted(() => {
|
||||
useResizeObserver(contentRef, async () => {
|
||||
await nextTick();
|
||||
delay(60).then(() => {
|
||||
treeHeight.value = parseFloat(
|
||||
subBefore(tableRef.value.getTableDoms().tableWrapper.style.height, "px")
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:inline="true"
|
||||
:model="form"
|
||||
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto"
|
||||
>
|
||||
<el-form-item label="角色名称:" prop="name">
|
||||
<el-input
|
||||
v-model="form.name"
|
||||
placeholder="请输入角色名称"
|
||||
clearable
|
||||
class="!w-[180px]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色标识:" prop="code">
|
||||
<el-input
|
||||
v-model="form.code"
|
||||
placeholder="请输入角色标识"
|
||||
clearable
|
||||
class="!w-[180px]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:" prop="status">
|
||||
<el-select
|
||||
v-model="form.status"
|
||||
placeholder="请选择状态"
|
||||
clearable
|
||||
class="!w-[180px]"
|
||||
>
|
||||
<el-option label="已启用" value="1" />
|
||||
<el-option label="已停用" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon('ri:search-line')"
|
||||
:loading="loading"
|
||||
@click="onSearch"
|
||||
>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div
|
||||
ref="contentRef"
|
||||
:class="['flex', deviceDetection() ? 'flex-wrap' : '']"
|
||||
>
|
||||
<PureTableBar
|
||||
:class="[isShow && !deviceDetection() ? '!w-[60vw]' : 'w-full']"
|
||||
style="transition: width 220ms cubic-bezier(0.4, 0, 0.2, 1)"
|
||||
title="角色管理(仅演示,操作后不生效)"
|
||||
:columns="columns"
|
||||
@refresh="onSearch"
|
||||
>
|
||||
<template #buttons>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon(AddFill)"
|
||||
@click="openDialog()"
|
||||
>
|
||||
新增角色
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-slot="{ size, dynamicColumns }">
|
||||
<pure-table
|
||||
ref="tableRef"
|
||||
align-whole="center"
|
||||
showOverflowTooltip
|
||||
table-layout="auto"
|
||||
:loading="loading"
|
||||
:size="size"
|
||||
adaptive
|
||||
:row-style="rowStyle"
|
||||
:adaptiveConfig="{ offsetBottom: 108 }"
|
||||
:data="dataList"
|
||||
:columns="dynamicColumns"
|
||||
:pagination="{ ...pagination, size }"
|
||||
:header-cell-style="{
|
||||
background: 'var(--el-fill-color-light)',
|
||||
color: 'var(--el-text-color-primary)'
|
||||
}"
|
||||
@selection-change="handleSelectionChange"
|
||||
@page-size-change="handleSizeChange"
|
||||
@page-current-change="handleCurrentChange"
|
||||
>
|
||||
<template #operation="{ row }">
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(EditPen)"
|
||||
@click="openDialog('修改', row)"
|
||||
>
|
||||
修改
|
||||
</el-button>
|
||||
<el-popconfirm
|
||||
:title="`是否确认删除角色名称为${row.name}的这条数据`"
|
||||
@confirm="handleDelete(row)"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(Delete)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(Menu)"
|
||||
@click="handleMenu(row)"
|
||||
>
|
||||
权限
|
||||
</el-button>
|
||||
<!-- <el-dropdown>
|
||||
<el-button
|
||||
class="ml-3 mt-[2px]"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(More)"
|
||||
/>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>
|
||||
<el-button
|
||||
:class="buttonClass"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(Menu)"
|
||||
@click="handleMenu"
|
||||
>
|
||||
菜单权限
|
||||
</el-button>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<el-button
|
||||
:class="buttonClass"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(Database)"
|
||||
@click="handleDatabase"
|
||||
>
|
||||
数据权限
|
||||
</el-button>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown> -->
|
||||
</template>
|
||||
</pure-table>
|
||||
</template>
|
||||
</PureTableBar>
|
||||
|
||||
<div
|
||||
v-if="isShow"
|
||||
class="!min-w-[calc(100vw-60vw-268px)] w-full mt-2 px-2 pb-2 bg-bg_color ml-2 overflow-auto"
|
||||
>
|
||||
<div class="flex justify-between w-full px-3 pt-5 pb-4">
|
||||
<div class="flex">
|
||||
<span :class="iconClass">
|
||||
<IconifyIconOffline
|
||||
v-tippy="{
|
||||
content: '关闭'
|
||||
}"
|
||||
class="dark:text-white"
|
||||
width="18px"
|
||||
height="18px"
|
||||
:icon="Close"
|
||||
@click="handleMenu"
|
||||
/>
|
||||
</span>
|
||||
<span :class="[iconClass, 'ml-2']">
|
||||
<IconifyIconOffline
|
||||
v-tippy="{
|
||||
content: '保存菜单权限'
|
||||
}"
|
||||
class="dark:text-white"
|
||||
width="18px"
|
||||
height="18px"
|
||||
:icon="Check"
|
||||
@click="handleSave"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<p class="font-bold truncate">
|
||||
菜单权限
|
||||
{{ `${curRow?.name ? `(${curRow.name})` : ""}` }}
|
||||
</p>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="treeSearchValue"
|
||||
placeholder="请输入菜单进行搜索"
|
||||
class="mb-1"
|
||||
clearable
|
||||
@input="onQueryChanged"
|
||||
/>
|
||||
<div class="flex flex-wrap">
|
||||
<el-checkbox v-model="isExpandAll" label="展开/折叠" />
|
||||
<el-checkbox v-model="isSelectAll" label="全选/全不选" />
|
||||
<el-checkbox v-model="isLinkage" label="父子联动" />
|
||||
</div>
|
||||
<el-tree-v2
|
||||
ref="treeRef"
|
||||
show-checkbox
|
||||
:data="treeData"
|
||||
:props="treeProps"
|
||||
:height="treeHeight"
|
||||
:check-strictly="!isLinkage"
|
||||
:filter-method="filterMethod"
|
||||
>
|
||||
<template #default="{ node }">
|
||||
<span>{{ transformI18n(node.label) }}</span>
|
||||
</template>
|
||||
</el-tree-v2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.el-dropdown-menu__item i) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin: 24px 24px 0 !important;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,318 +0,0 @@
|
|||
import dayjs from "dayjs";
|
||||
import editForm from "../form.vue";
|
||||
import { handleTree } from "@/utils/tree";
|
||||
import { message } from "@/utils/message";
|
||||
import { ElMessageBox } from "element-plus";
|
||||
import { usePublicHooks } from "../../hooks";
|
||||
import { addDialog } from "@/components/BaseDialog";
|
||||
import type { FormItemProps } from "../utils/types";
|
||||
import type { PaginationProps } from "@pureadmin/table";
|
||||
import { deviceDetection, getKeyList } from "@pureadmin/utils";
|
||||
import { getRoleList, getRoleMenu, getRoleMenuIds } from "@/api/v1/system";
|
||||
import { h, onMounted, reactive, type Ref, ref, toRaw, watch } from "vue";
|
||||
import { $t } from "@/plugins/i18n";
|
||||
|
||||
export function useRole(treeRef: Ref) {
|
||||
const form = reactive({
|
||||
name: "",
|
||||
code: "",
|
||||
status: ""
|
||||
});
|
||||
const curRow = ref();
|
||||
const formRef = ref();
|
||||
const dataList = ref([]);
|
||||
const treeIds = ref([]);
|
||||
const treeData = ref([]);
|
||||
const isShow = ref(false);
|
||||
const loading = ref(true);
|
||||
const isLinkage = ref(false);
|
||||
const treeSearchValue = ref();
|
||||
const switchLoadMap = ref({});
|
||||
const isExpandAll = ref(false);
|
||||
const isSelectAll = ref(false);
|
||||
const { switchStyle } = usePublicHooks();
|
||||
const treeProps = {
|
||||
value: "id",
|
||||
label: "title",
|
||||
children: "children"
|
||||
};
|
||||
const pagination = reactive<PaginationProps>({
|
||||
total: 0,
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
background: true
|
||||
});
|
||||
const columns: TableColumnList = [
|
||||
{
|
||||
label: "角色编号",
|
||||
prop: "id"
|
||||
},
|
||||
{
|
||||
label: "角色名称",
|
||||
prop: "name"
|
||||
},
|
||||
{
|
||||
label: "角色标识",
|
||||
prop: "code"
|
||||
},
|
||||
{
|
||||
label: "状态",
|
||||
cellRenderer: scope => (
|
||||
<el-switch
|
||||
size={scope.props.size === "small" ? "small" : "default"}
|
||||
loading={switchLoadMap.value[scope.index]?.loading}
|
||||
v-model={scope.row.status}
|
||||
active-value={1}
|
||||
inactive-value={0}
|
||||
active-text="已启用"
|
||||
inactive-text="已停用"
|
||||
inline-prompt
|
||||
style={switchStyle.value}
|
||||
onChange={() => onChange(scope as any)}
|
||||
/>
|
||||
),
|
||||
minWidth: 90
|
||||
},
|
||||
{
|
||||
label: "备注",
|
||||
prop: "remark",
|
||||
minWidth: 160
|
||||
},
|
||||
{
|
||||
label: "创建时间",
|
||||
prop: "createTime",
|
||||
minWidth: 160,
|
||||
formatter: ({ createTime }) =>
|
||||
dayjs(createTime).format("YYYY-MM-DD HH:mm:ss")
|
||||
},
|
||||
{
|
||||
label: "操作",
|
||||
fixed: "right",
|
||||
width: 210,
|
||||
slot: "operation"
|
||||
}
|
||||
];
|
||||
// const buttonClass = computed(() => {
|
||||
// return [
|
||||
// "!h-[20px]",
|
||||
// "reset-margin",
|
||||
// "!text-gray-500",
|
||||
// "dark:!text-white",
|
||||
// "dark:hover:!text-primary"
|
||||
// ];
|
||||
// });
|
||||
|
||||
function onChange({ row, index }) {
|
||||
ElMessageBox.confirm(
|
||||
`确认要<strong>${
|
||||
row.status === 0 ? "停用" : "启用"
|
||||
}</strong><strong style='color:var(--el-color-primary)'>${
|
||||
row.name
|
||||
}</strong>吗?`,
|
||||
"系统提示",
|
||||
{
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
dangerouslyUseHTMLString: true,
|
||||
draggable: true
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
switchLoadMap.value[index] = Object.assign(
|
||||
{},
|
||||
switchLoadMap.value[index],
|
||||
{
|
||||
loading: true
|
||||
}
|
||||
);
|
||||
setTimeout(() => {
|
||||
switchLoadMap.value[index] = Object.assign(
|
||||
{},
|
||||
switchLoadMap.value[index],
|
||||
{
|
||||
loading: false
|
||||
}
|
||||
);
|
||||
message(`已${row.status === 0 ? "停用" : "启用"}${row.name}`, {
|
||||
type: "success"
|
||||
});
|
||||
}, 300);
|
||||
})
|
||||
.catch(() => {
|
||||
row.status === 0 ? (row.status = 1) : (row.status = 0);
|
||||
});
|
||||
}
|
||||
|
||||
function handleDelete(row) {
|
||||
message(`您删除了角色名称为${row.name}的这条数据`, { type: "success" });
|
||||
onSearch();
|
||||
}
|
||||
|
||||
function handleSizeChange(val: number) {
|
||||
console.log(`${val} items per page`);
|
||||
}
|
||||
|
||||
function handleCurrentChange(val: number) {
|
||||
console.log(`current page: ${val}`);
|
||||
}
|
||||
|
||||
function handleSelectionChange(val) {
|
||||
console.log("handleSelectionChange", val);
|
||||
}
|
||||
|
||||
async function onSearch() {
|
||||
loading.value = true;
|
||||
const { data } = await getRoleList(toRaw(form));
|
||||
dataList.value = data.list;
|
||||
pagination.total = data.total;
|
||||
pagination.pageSize = data.pageSize;
|
||||
pagination.currentPage = data.currentPage;
|
||||
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
const resetForm = formEl => {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
onSearch();
|
||||
};
|
||||
|
||||
function openDialog(title = "新增", row?: FormItemProps) {
|
||||
addDialog({
|
||||
title: `${title}角色`,
|
||||
props: {
|
||||
formInline: {
|
||||
name: row?.name ?? "",
|
||||
code: row?.code ?? "",
|
||||
remark: row?.remark ?? ""
|
||||
}
|
||||
},
|
||||
width: "40%",
|
||||
draggable: true,
|
||||
fullscreen: deviceDetection(),
|
||||
fullscreenIcon: true,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () => h(editForm, { ref: formRef, formInline: null }),
|
||||
beforeSure: (done, { options }) => {
|
||||
const FormRef = formRef.value.getRef();
|
||||
const curData = options.props.formInline as FormItemProps;
|
||||
|
||||
function chores() {
|
||||
message(`您${title}了角色名称为${curData.name}的这条数据`, {
|
||||
type: "success"
|
||||
});
|
||||
done(); // 关闭弹框
|
||||
onSearch(); // 刷新表格数据
|
||||
}
|
||||
|
||||
FormRef.validate(valid => {
|
||||
if (valid) {
|
||||
console.log("curData", curData);
|
||||
// 表单规则校验通过
|
||||
if (title === "新增") {
|
||||
// 实际开发先调用新增接口,再进行下面操作
|
||||
chores();
|
||||
} else {
|
||||
// 实际开发先调用修改接口,再进行下面操作
|
||||
chores();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 菜单权限 */
|
||||
async function handleMenu(row?: any) {
|
||||
const { id } = row;
|
||||
if (id) {
|
||||
curRow.value = row;
|
||||
isShow.value = true;
|
||||
const { data } = await getRoleMenuIds({ id });
|
||||
treeRef.value.setCheckedKeys(data);
|
||||
} else {
|
||||
curRow.value = null;
|
||||
isShow.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 高亮当前权限选中行 */
|
||||
function rowStyle({ row: { id } }) {
|
||||
return {
|
||||
cursor: "pointer",
|
||||
background: id === curRow.value?.id ? "var(--el-fill-color-light)" : ""
|
||||
};
|
||||
}
|
||||
|
||||
/** 菜单权限-保存 */
|
||||
function handleSave() {
|
||||
const { id, name } = curRow.value;
|
||||
// 根据用户 id 调用实际项目中菜单权限修改接口
|
||||
console.log(id, treeRef.value.getCheckedKeys());
|
||||
message(`角色名称为${name}的菜单权限修改成功`, {
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
|
||||
/** 数据权限 可自行开发 */
|
||||
// function handleDatabase() {}
|
||||
|
||||
const onQueryChanged = (query: string) => {
|
||||
treeRef.value!.filter(query);
|
||||
};
|
||||
|
||||
const filterMethod = (query: string, node) => {
|
||||
return $t(node.title)!.includes(query);
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
onSearch();
|
||||
const { data } = await getRoleMenu();
|
||||
treeIds.value = getKeyList(data, "id");
|
||||
treeData.value = handleTree(data);
|
||||
});
|
||||
|
||||
watch(isExpandAll, val => {
|
||||
val
|
||||
? treeRef.value.setExpandedKeys(treeIds.value)
|
||||
: treeRef.value.setExpandedKeys([]);
|
||||
});
|
||||
|
||||
watch(isSelectAll, val => {
|
||||
val
|
||||
? treeRef.value.setCheckedKeys(treeIds.value)
|
||||
: treeRef.value.setCheckedKeys([]);
|
||||
});
|
||||
|
||||
return {
|
||||
form,
|
||||
isShow,
|
||||
curRow,
|
||||
loading,
|
||||
columns,
|
||||
rowStyle,
|
||||
dataList,
|
||||
treeData,
|
||||
treeProps,
|
||||
isLinkage,
|
||||
pagination,
|
||||
isExpandAll,
|
||||
isSelectAll,
|
||||
treeSearchValue,
|
||||
onSearch,
|
||||
resetForm,
|
||||
openDialog,
|
||||
handleMenu,
|
||||
handleSave,
|
||||
handleDelete,
|
||||
filterMethod,
|
||||
$t,
|
||||
onQueryChanged,
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
handleSelectionChange
|
||||
};
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import { reactive } from "vue";
|
||||
import type { FormRules } from "element-plus";
|
||||
|
||||
/** 自定义表单规则校验 */
|
||||
export const formRules = reactive(<FormRules>{
|
||||
name: [{ required: true, message: "角色名称为必填项", trigger: "blur" }],
|
||||
code: [{ required: true, message: "角色标识为必填项", trigger: "blur" }]
|
||||
});
|
|
@ -1,15 +0,0 @@
|
|||
// 虽然字段很少 但是抽离出来 后续有扩展字段需求就很方便了
|
||||
|
||||
interface FormItemProps {
|
||||
/** 角色名称 */
|
||||
name: string;
|
||||
/** 角色编号 */
|
||||
code: string;
|
||||
/** 备注 */
|
||||
remark: string;
|
||||
}
|
||||
interface FormProps {
|
||||
formInline: FormItemProps;
|
||||
}
|
||||
|
||||
export type { FormItemProps, FormProps };
|
|
@ -1,25 +1,18 @@
|
|||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import ReCol from '@/components/MyCol';
|
||||
import { formRules } from '@/views/system/menu/utils/rule';
|
||||
import { formRules } from '@/views/system/menu/utils/columns';
|
||||
import IconSelect from '@/components/SelectIcon/Select.vue';
|
||||
import Segmented from '@/components/Segmented';
|
||||
import { menuTypeOptions } from '@/enums';
|
||||
import { FormProps } from '@/views/system/menu/utils/types';
|
||||
import { IconSelect } from '@/components/ReIcon';
|
||||
import Segmented from '@/components/ReSegmented';
|
||||
import { menuTypeOptions, showLinkOptions } from '@/enums';
|
||||
import { userMenuIconStore } from '@/store/modules/menuIcon';
|
||||
|
||||
const props = withDefaults(defineProps<FormProps>(), {
|
||||
formInline: () => ({}),
|
||||
});
|
||||
|
||||
const menuIconStore = userMenuIconStore();
|
||||
const ruleFormRef = ref();
|
||||
const newFormInline = ref(props.formInline);
|
||||
|
||||
onMounted(() => {
|
||||
menuIconStore.getMenuIconList();
|
||||
});
|
||||
|
||||
defineExpose({ menuFormRef: ruleFormRef });
|
||||
</script>
|
||||
|
||||
|
@ -81,7 +74,7 @@ defineExpose({ menuFormRef: ruleFormRef });
|
|||
|
||||
<re-col :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="菜单图标" prop="icon">
|
||||
<IconSelect v-model="newFormInline.icon" class="w-full" />
|
||||
<IconSelect :form-inline="newFormInline" class="w-full" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
|
@ -93,7 +86,9 @@ defineExpose({ menuFormRef: ruleFormRef });
|
|||
|
||||
<re-col :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="是否显示">
|
||||
<Segmented :modelValue="newFormInline.visible ? 0 : 1" :options="showLinkOptions" @change="({ option: { value } }) => (newFormInline.visible = value)" />
|
||||
<el-switch v-model="newFormInline.visible" active-text="开启" inactive-text="隐藏" inline-prompt style="
|
||||
|
||||
--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
</el-row>
|
||||
|
|
|
@ -2,25 +2,48 @@
|
|||
import { onMounted, ref } from 'vue';
|
||||
import { $t } from '@/plugins/i18n';
|
||||
import PureTableBar from '@/components/TableBar/src/bar';
|
||||
import { useRenderIcon } from '@/components/ReIcon/src/hooks';
|
||||
import Delete from '@iconify-icons/ep/delete';
|
||||
import EditPen from '@iconify-icons/ep/edit-pen';
|
||||
import Refresh from '@iconify-icons/ep/refresh';
|
||||
import AddFill from '@iconify-icons/ri/add-circle-line';
|
||||
import { handleDelete, onAdd, onSearch, onUpdate, resetForm } from '@/views/system/menu/utils/hook';
|
||||
import form from '@/views/role/form.vue';
|
||||
import form from '@/views/system/menu/form.vue';
|
||||
import PureTable from '@pureadmin/table';
|
||||
import { columns } from '@/views/system/menu/utils/rule';
|
||||
import { columns } from '@/views/system/menu/utils/columns';
|
||||
import { userRouterStore } from '@/store/modules/router';
|
||||
|
||||
defineOptions({
|
||||
name: 'SystemMenu',
|
||||
});
|
||||
import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
|
||||
|
||||
const formRef = ref();
|
||||
const tableRef = ref();
|
||||
const routerStore = userRouterStore();
|
||||
|
||||
/**
|
||||
* * 修改菜单是否显示
|
||||
* @param val
|
||||
*/
|
||||
const onchangeVisible = async (val: boolean) => {
|
||||
const data = {
|
||||
id: val.id,
|
||||
visible: val.visible,
|
||||
menuType: val.menuType,
|
||||
title: val.title,
|
||||
name: val.name,
|
||||
path: val.path,
|
||||
};
|
||||
await routerStore.updateMenu(data);
|
||||
await onSearch();
|
||||
};
|
||||
|
||||
/**
|
||||
* 表单重置
|
||||
* @param formEl
|
||||
*/
|
||||
const resetForm = async (formEl: any) => {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
await onSearch();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
onSearch();
|
||||
});
|
||||
|
@ -30,7 +53,7 @@ onMounted(() => {
|
|||
<div class="main">
|
||||
<el-form ref="formRef" :inline="true" :model="form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
|
||||
<el-form-item label="菜单名称" prop="title">
|
||||
<el-input v-model="form.title" class="!w-[180px]" clearable placeholder="输入菜单名称" />
|
||||
<el-input v-model="routerStore.form.title" class="!w-[180px]" clearable placeholder="输入菜单名称" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button :icon="useRenderIcon('ri:search-line')" :loading="routerStore.loading" type="primary" @click="onSearch"> 搜索 </el-button>
|
||||
|
@ -53,10 +76,27 @@ onMounted(() => {
|
|||
:size="size"
|
||||
adaptive
|
||||
align-whole="center"
|
||||
border
|
||||
highlight-current-row
|
||||
row-key="id"
|
||||
showOverflowTooltip
|
||||
table-layout="auto"
|
||||
>
|
||||
<template #visible="{ row }">
|
||||
<el-switch
|
||||
v-model="row.visible"
|
||||
active-text="显示"
|
||||
class="ml-2"
|
||||
inactive-text="隐藏"
|
||||
inline-prompt
|
||||
style="
|
||||
|
||||
--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
|
||||
width="60"
|
||||
@update:modelValue="onchangeVisible(row)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #operation="{ row }">
|
||||
<el-button :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="onUpdate(row)"> 修改 </el-button>
|
||||
<el-button v-show="row.menuType !== 3" :icon="useRenderIcon(AddFill)" :size="size" class="reset-margin" link type="primary" @click="onAdd(row.id)"> 新增 </el-button>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { h, reactive } from 'vue';
|
||||
import type { FormRules } from 'element-plus';
|
||||
import { useRenderIcon } from '@/components/ReIcon/src/hooks';
|
||||
import { $t } from '@/plugins/i18n';
|
||||
import { isAllEmpty } from '@pureadmin/utils';
|
||||
import { getMenuType } from '@/views/system/menu/utils/hook';
|
||||
import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
|
||||
|
||||
export const columns: TableColumnList = [
|
||||
{
|
||||
|
@ -37,14 +37,8 @@ export const columns: TableColumnList = [
|
|||
prop: 'component',
|
||||
formatter: ({ path, component }) => (isAllEmpty(component) ? path : component),
|
||||
},
|
||||
{ label: '权限标识', prop: 'auths' },
|
||||
{ label: '排序', prop: 'rank', width: 100 },
|
||||
{
|
||||
label: '隐藏',
|
||||
prop: 'showLink',
|
||||
formatter: ({ showLink }) => (showLink ? '否' : '是'),
|
||||
width: 100,
|
||||
},
|
||||
{ label: '隐藏', prop: 'visible', slot: 'visible', width: 100 },
|
||||
{ label: '操作', fixed: 'right', width: 210, slot: 'operation' },
|
||||
];
|
||||
|
|
@ -29,16 +29,6 @@ export const getMenuType = (type, text = false) => {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 表单重置
|
||||
* @param formEl
|
||||
*/
|
||||
export const resetForm = async (formEl: any) => {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
await onSearch();
|
||||
};
|
||||
|
||||
/**
|
||||
* * 获取菜单数据
|
||||
*/
|
||||
|
@ -93,14 +83,12 @@ export function onAdd(parentId: any = 0) {
|
|||
if (!valid) return;
|
||||
delete curData.higherMenuOptions;
|
||||
|
||||
console.log(curData);
|
||||
|
||||
// const result = await routerStore.addMenu(curData);
|
||||
// // 刷新表格数据
|
||||
// if (result) {
|
||||
// done();
|
||||
// await onSearch();
|
||||
// }
|
||||
const result = await routerStore.addMenu(curData);
|
||||
// 刷新表格数据
|
||||
if (result) {
|
||||
done();
|
||||
await onSearch();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -125,7 +113,7 @@ export const onUpdate = (row?: FormItemProps) => {
|
|||
rank: row?.rank,
|
||||
icon: row?.icon,
|
||||
frameSrc: row?.frameSrc,
|
||||
visible: row?.visible,
|
||||
visible: row.visible,
|
||||
},
|
||||
},
|
||||
width: '45%',
|
||||
|
@ -142,13 +130,12 @@ export const onUpdate = (row?: FormItemProps) => {
|
|||
delete curData.higherMenuOptions;
|
||||
|
||||
curData.id = row.id;
|
||||
console.log(row);
|
||||
// const result = await routerStore.updateMenu(curData);
|
||||
// // 刷新表格数据
|
||||
// if (result) {
|
||||
// done();
|
||||
// await onSearch();
|
||||
// }
|
||||
const result = await routerStore.updateMenu(curData);
|
||||
// 刷新表格数据
|
||||
if (result) {
|
||||
done();
|
||||
await onSearch();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { columns } from '@/views/system/menuIcon/utils/columns';
|
||||
import PureTableBar from '@/components/TableBar/src/bar';
|
||||
import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
|
||||
import AddFill from '@iconify-icons/ri/add-circle-line';
|
||||
import PureTable from '@pureadmin/table';
|
||||
import { onAdd, onDelete, onSearch, onUpdate } from '@/views/system/menuIcon/utils/hook';
|
||||
import Delete from '@iconify-icons/ep/delete';
|
||||
import EditPen from '@iconify-icons/ep/edit-pen';
|
||||
import Refresh from '@iconify-icons/ep/refresh';
|
||||
import { selectUserinfo } from '@/components/Table/Userinfo/columns';
|
||||
import { $t } from '@/plugins/i18n';
|
||||
import { useMenuIconStore } from '@/store/modules/menuIcon';
|
||||
|
||||
const tableRef = ref();
|
||||
const formRef = ref();
|
||||
const menuIconStore = useMenuIconStore();
|
||||
|
||||
const resetForm = async formEl => {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
await onSearch();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
onSearch();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="main">
|
||||
<el-form ref="formRef" :inline="true" :model="menuIconStore.form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
|
||||
<el-form-item :label="$t('menuIcon_iconName')" prop="iconName">
|
||||
<el-input v-model="menuIconStore.form.iconName" :placeholder="`${$t('input')} ${$t('iconName')}`" class="!w-[180px]" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button :icon="useRenderIcon('ri:search-line')" :loading="menuIconStore.loading" type="primary" @click="onSearch"> {{ $t('search') }} </el-button>
|
||||
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> {{ $t('buttons.reset') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<PureTableBar :columns="columns" title="系统菜单图标" @fullscreen="tableRef.setAdaptive()" @refresh="onSearch">
|
||||
<template #buttons>
|
||||
<el-button :icon="useRenderIcon(AddFill)" type="primary" @click="onAdd"> 添加系统菜单图标</el-button>
|
||||
</template>
|
||||
|
||||
<template v-slot="{ size, dynamicColumns }">
|
||||
<pure-table
|
||||
ref="tableRef"
|
||||
:adaptiveConfig="{ offsetBottom: 45 }"
|
||||
:columns="dynamicColumns"
|
||||
:data="menuIconStore.datalist"
|
||||
:header-cell-style="{ background: 'var(--el-fill-color-light)', color: 'var(--el-text-color-primary)' }"
|
||||
:loading="menuIconStore.loading"
|
||||
:size="size"
|
||||
adaptive
|
||||
align-whole="center"
|
||||
border
|
||||
highlight-current-row
|
||||
row-key="id"
|
||||
showOverflowTooltip
|
||||
table-layout="auto"
|
||||
>
|
||||
<template #iconName="{ row }">
|
||||
<div class="flex justify-center">
|
||||
<component :is="useRenderIcon(row.iconName)" class="flex justify-center" style="font-size: 30px" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #createUser="{ row }">
|
||||
<el-button link type="primary" @click="selectUserinfo(row.createUser)">{{ $t('table.createUser') }} </el-button>
|
||||
</template>
|
||||
|
||||
<template #updateUser="{ row }">
|
||||
<el-button link type="primary" @click="selectUserinfo(row.updateUser)">{{ $t('table.updateUser') }} </el-button>
|
||||
</template>
|
||||
|
||||
<template #operation="{ row }">
|
||||
<el-button :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="onUpdate(row)"> {{ $t('modify') }} </el-button>
|
||||
<el-button :icon="useRenderIcon(AddFill)" :size="size" class="reset-margin" link type="primary" @click="onAdd"> {{ $t('add_new') }} </el-button>
|
||||
<el-popconfirm :title="`是否确认删除 ${row.iconName}数据`" @confirm="onDelete(row)">
|
||||
<template #reference>
|
||||
<el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary">
|
||||
{{ $t('delete') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</pure-table>
|
||||
</template>
|
||||
</PureTableBar>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,34 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { rules } from '@/views/system/menuIcon/utils/columns';
|
||||
import { FormProps } from '@/views/system/menuIcon/utils/types';
|
||||
import { $t } from '@/plugins/i18n';
|
||||
import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
|
||||
|
||||
const props = withDefaults(defineProps<FormProps>(), {
|
||||
formInline: () => ({
|
||||
// icon 名称
|
||||
iconName: undefined,
|
||||
}),
|
||||
});
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
const form = ref(props.formInline);
|
||||
|
||||
defineExpose({ formRef });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="auto">
|
||||
<el-form-item :label="$t('menuIcon_iconName')" prop="iconName">
|
||||
<el-input v-model="form.iconName" autocomplete="off" type="text" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('menuIcon_preview')">
|
||||
<div class="flex justify-center">
|
||||
<component :is="useRenderIcon(form.iconName)" class="flex justify-center" style="font-size: 30px" />
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
|
@ -0,0 +1,22 @@
|
|||
import { reactive } from 'vue';
|
||||
import { $t } from '@/plugins/i18n';
|
||||
|
||||
// 表格列
|
||||
export const columns: TableColumnList = [
|
||||
{ type: 'index', index: (index: number) => index + 1 },
|
||||
{ type: 'selection', align: 'left' },
|
||||
{ label: $t('id'), prop: 'id' },
|
||||
// icon 名称
|
||||
{ label: $t('menuIcon_iconName'), prop: 'iconName', slot: 'iconName' },
|
||||
{ label: $t('table.updateTime'), prop: 'updateTime', sortable: true },
|
||||
{ label: $t('table.createTime'), prop: 'createTime', sortable: true },
|
||||
{ label: $t('table.createUser'), prop: 'createUser', slot: 'createUser' },
|
||||
{ label: $t('table.updateUser'), prop: 'updateUser', slot: 'updateUser' },
|
||||
{ label: $t('table.operation'), fixed: 'right', width: 210, slot: 'operation' },
|
||||
];
|
||||
|
||||
// 添加规则
|
||||
export const rules = reactive({
|
||||
// icon 名称
|
||||
iconName: [{ required: true, message: `${$t('input')}${$t('menuIcon_iconName')}`, trigger: 'blur' }],
|
||||
});
|
|
@ -0,0 +1,103 @@
|
|||
import { deviceDetection } from '@pureadmin/utils';
|
||||
import { addDialog } from '@/components/BaseDialog/index';
|
||||
import MenuIconDialog from '@/views/system/menuIcon/menu-icon-dialog.vue';
|
||||
import { useMenuIconStore } from '@/store/modules/menuIcon';
|
||||
import { h, ref } from 'vue';
|
||||
import { messageBox } from '@/utils/message';
|
||||
import type { FormItemProps } from '@/views/system/menuIcon/utils/types';
|
||||
import { $t } from '@/plugins/i18n';
|
||||
|
||||
export const formRef = ref();
|
||||
const menuIconStore = useMenuIconStore();
|
||||
|
||||
/**
|
||||
* * 搜索初始化系统菜单图标
|
||||
*/
|
||||
export async function onSearch() {
|
||||
menuIconStore.loading = true;
|
||||
await menuIconStore.getMenuIconList();
|
||||
menuIconStore.loading = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* * 添加${系统菜单图标}
|
||||
*/
|
||||
export function onAdd() {
|
||||
addDialog({
|
||||
title: `${$t('add_new')} ${$t('menuIcon')}`,
|
||||
width: '30%',
|
||||
props: {
|
||||
formInline: {
|
||||
iconName: undefined,
|
||||
},
|
||||
},
|
||||
draggable: true,
|
||||
fullscreen: deviceDetection(),
|
||||
fullscreenIcon: true,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () => h(MenuIconDialog, { ref: formRef }),
|
||||
beforeSure: (done, { options }) => {
|
||||
const form = options.props.formInline as FormItemProps;
|
||||
formRef.value.formRef.validate(async (valid: any) => {
|
||||
if (!valid) return;
|
||||
|
||||
const result = await menuIconStore.addMenuIcon(form);
|
||||
if (!result) return;
|
||||
done();
|
||||
await onSearch();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* * 更新系统菜单图标
|
||||
* @param row
|
||||
*/
|
||||
export function onUpdate(row: any) {
|
||||
addDialog({
|
||||
title: `${$t('modify')} ${$t('menuIcon')}`,
|
||||
width: '30%',
|
||||
props: {
|
||||
formInline: {
|
||||
iconName: row.iconName,
|
||||
},
|
||||
},
|
||||
draggable: true,
|
||||
fullscreen: deviceDetection(),
|
||||
fullscreenIcon: true,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () => h(MenuIconDialog, { ref: formRef }),
|
||||
beforeSure: (done, { options }) => {
|
||||
const form = options.props.formInline as FormItemProps;
|
||||
formRef.value.formRef.validate(async (valid: any) => {
|
||||
if (!valid) return;
|
||||
|
||||
const result = await menuIconStore.updateMenuIcon({ ...form, id: row.id });
|
||||
if (!result) return;
|
||||
done();
|
||||
await onSearch();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* * 删除系统菜单图标
|
||||
*/
|
||||
export const onDelete = async (row: any) => {
|
||||
const id = row.id;
|
||||
|
||||
// 是否确认删除
|
||||
const result = await messageBox({
|
||||
title: $t('confirm_delete'),
|
||||
showMessage: false,
|
||||
confirmMessage: undefined,
|
||||
cancelMessage: $t('cancel_delete'),
|
||||
});
|
||||
if (!result) return;
|
||||
|
||||
// 删除数据
|
||||
await menuIconStore.deleteMenuIcon([id]);
|
||||
await onSearch();
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
// 添加或者修改表单元素
|
||||
export interface FormItemProps {
|
||||
// icon 名称
|
||||
iconName: string;
|
||||
}
|
||||
|
||||
// 添加或修改表单Props
|
||||
export interface FormProps {
|
||||
formInline: FormItemProps;
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import ReCol from '@/components/MyCol';
|
||||
import { formRules } from '../utils/rule';
|
||||
import { FormProps } from '../utils/types';
|
||||
import { usePublicHooks } from '../../hooks';
|
||||
|
||||
const props = withDefaults(defineProps<FormProps>(), {
|
||||
formInline: () => ({
|
||||
title: '新增',
|
||||
higherDeptOptions: [],
|
||||
parentId: 0,
|
||||
nickname: '',
|
||||
username: '',
|
||||
password: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
sex: '',
|
||||
status: 1,
|
||||
remark: '',
|
||||
}),
|
||||
});
|
||||
|
||||
const sexOptions = [
|
||||
{
|
||||
value: 0,
|
||||
label: '男',
|
||||
},
|
||||
{
|
||||
value: 1,
|
||||
label: '女',
|
||||
},
|
||||
];
|
||||
const ruleFormRef = ref();
|
||||
const { switchStyle } = usePublicHooks();
|
||||
const newFormInline = ref(props.formInline);
|
||||
|
||||
function getRef() {
|
||||
return ruleFormRef.value;
|
||||
}
|
||||
|
||||
defineExpose({ getRef });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form ref="ruleFormRef" :model="newFormInline" :rules="formRules" label-width="82px">
|
||||
<el-row :gutter="30">
|
||||
<re-col :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="用户昵称" prop="nickname">
|
||||
<el-input v-model="newFormInline.nickname" clearable placeholder="请输入用户昵称" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="用户名称" prop="username">
|
||||
<el-input v-model="newFormInline.username" clearable placeholder="请输入用户名称" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col v-if="newFormInline.title === '新增'" :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="用户密码" prop="password">
|
||||
<el-input v-model="newFormInline.password" clearable placeholder="请输入用户密码" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input v-model="newFormInline.phone" clearable placeholder="请输入手机号" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="newFormInline.email" clearable placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="用户性别">
|
||||
<el-select v-model="newFormInline.sex" class="w-full" clearable placeholder="请选择用户性别">
|
||||
<el-option v-for="(item, index) in sexOptions" :key="index" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="归属部门">
|
||||
<el-cascader
|
||||
v-model="newFormInline.parentId"
|
||||
:options="newFormInline.higherDeptOptions"
|
||||
:props="{
|
||||
value: 'id',
|
||||
label: 'name',
|
||||
emitPath: false,
|
||||
checkStrictly: true,
|
||||
}"
|
||||
class="w-full"
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择归属部门"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<span>{{ data.name }}</span>
|
||||
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
|
||||
</template>
|
||||
</el-cascader>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col v-if="newFormInline.title === '新增'" :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="用户状态">
|
||||
<el-switch v-model="newFormInline.status" :active-value="1" :inactive-value="0" :style="switchStyle" active-text="启用" inactive-text="停用" inline-prompt />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="newFormInline.remark" placeholder="请输入备注信息" type="textarea" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
|
@ -1,42 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import ReCol from '@/components/MyCol';
|
||||
import { RoleFormProps } from '../utils/types';
|
||||
|
||||
const props = withDefaults(defineProps<RoleFormProps>(), {
|
||||
formInline: () => ({
|
||||
username: '',
|
||||
nickname: '',
|
||||
roleOptions: [],
|
||||
ids: [],
|
||||
}),
|
||||
});
|
||||
|
||||
const newFormInline = ref(props.formInline);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form :model="newFormInline">
|
||||
<el-row :gutter="30">
|
||||
<!-- <re-col>
|
||||
<el-form-item label="用户名称" prop="username">
|
||||
<el-input disabled v-model="newFormInline.username" />
|
||||
</el-form-item>
|
||||
</re-col> -->
|
||||
<re-col>
|
||||
<el-form-item label="用户昵称" prop="nickname">
|
||||
<el-input v-model="newFormInline.nickname" disabled />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col>
|
||||
<el-form-item label="角色列表" prop="ids">
|
||||
<el-select v-model="newFormInline.ids" class="w-full" clearable multiple placeholder="请选择">
|
||||
<el-option v-for="(item, index) in newFormInline.roleOptions" :key="index" :label="item.name" :value="item.id">
|
||||
{{ item.name }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
|
@ -1,161 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import tree from './tree.vue';
|
||||
import { useUser } from './utils/hook';
|
||||
import { PureTableBar } from '@/components/RePureTableBar';
|
||||
import { useRenderIcon } from '@/components/ReIcon/src/hooks';
|
||||
|
||||
import Upload from '@iconify-icons/ri/upload-line';
|
||||
import Role from '@iconify-icons/ri/admin-line';
|
||||
import Password from '@iconify-icons/ri/lock-password-line';
|
||||
import More from '@iconify-icons/ep/more-filled';
|
||||
import Delete from '@iconify-icons/ep/delete';
|
||||
import EditPen from '@iconify-icons/ep/edit-pen';
|
||||
import Refresh from '@iconify-icons/ep/refresh';
|
||||
import AddFill from '@iconify-icons/ri/add-circle-line';
|
||||
|
||||
defineOptions({
|
||||
name: 'SystemUser',
|
||||
});
|
||||
|
||||
const treeRef = ref();
|
||||
const formRef = ref();
|
||||
const tableRef = ref();
|
||||
|
||||
const {
|
||||
form,
|
||||
loading,
|
||||
columns,
|
||||
dataList,
|
||||
treeData,
|
||||
treeLoading,
|
||||
selectedNum,
|
||||
pagination,
|
||||
buttonClass,
|
||||
deviceDetection,
|
||||
onSearch,
|
||||
resetForm,
|
||||
onbatchDel,
|
||||
openDialog,
|
||||
onTreeSelect,
|
||||
handleUpdate,
|
||||
handleDelete,
|
||||
handleUpload,
|
||||
handleReset,
|
||||
handleRole,
|
||||
handleSizeChange,
|
||||
onSelectionCancel,
|
||||
handleCurrentChange,
|
||||
handleSelectionChange,
|
||||
} = useUser(tableRef, treeRef);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="['flex', 'justify-between', deviceDetection() && 'flex-wrap']">
|
||||
<tree ref="treeRef" :class="['mr-2', deviceDetection() ? 'w-full' : 'min-w-[200px]']" :treeData="treeData" :treeLoading="treeLoading" @tree-select="onTreeSelect" />
|
||||
<div :class="[deviceDetection() ? ['w-full', 'mt-2'] : 'w-[calc(100%-200px)]']">
|
||||
<el-form ref="formRef" :inline="true" :model="form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
|
||||
<el-form-item label="用户名称:" prop="username">
|
||||
<el-input v-model="form.username" class="!w-[180px]" clearable placeholder="请输入用户名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号码:" prop="phone">
|
||||
<el-input v-model="form.phone" class="!w-[180px]" clearable placeholder="请输入手机号码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:" prop="status">
|
||||
<el-select v-model="form.status" class="!w-[180px]" clearable placeholder="请选择">
|
||||
<el-option label="已开启" value="1" />
|
||||
<el-option label="已关闭" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button :icon="useRenderIcon('ri:search-line')" :loading="loading" type="primary" @click="onSearch"> 搜索 </el-button>
|
||||
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> 重置 </el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<PureTableBar :columns="columns" title="用户管理(仅演示,操作后不生效)" @refresh="onSearch">
|
||||
<template #buttons>
|
||||
<el-button :icon="useRenderIcon(AddFill)" type="primary" @click="openDialog()"> 新增用户 </el-button>
|
||||
</template>
|
||||
<template v-slot="{ size, dynamicColumns }">
|
||||
<div v-if="selectedNum > 0" v-motion-fade class="bg-[var(--el-fill-color-light)] w-full h-[46px] mb-2 pl-4 flex items-center">
|
||||
<div class="flex-auto">
|
||||
<span class="text-[rgba(42,46,54,0.5)] dark:text-[rgba(220,220,242,0.5)]" style="font-size: var(--el-font-size-base)"> 已选 {{ selectedNum }} 项 </span>
|
||||
<el-button text type="primary" @click="onSelectionCancel"> 取消选择 </el-button>
|
||||
</div>
|
||||
<el-popconfirm title="是否确认删除?" @confirm="onbatchDel">
|
||||
<template #reference>
|
||||
<el-button class="mr-1" text type="danger"> 批量删除 </el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</div>
|
||||
<pure-table
|
||||
ref="tableRef"
|
||||
:adaptiveConfig="{ offsetBottom: 108 }"
|
||||
:columns="dynamicColumns"
|
||||
:data="dataList"
|
||||
:header-cell-style="{
|
||||
background: 'var(--el-fill-color-light)',
|
||||
color: 'var(--el-text-color-primary)',
|
||||
}"
|
||||
:loading="loading"
|
||||
:pagination="{ ...pagination, size }"
|
||||
:size="size"
|
||||
adaptive
|
||||
align-whole="center"
|
||||
row-key="id"
|
||||
table-layout="auto"
|
||||
@selection-change="handleSelectionChange"
|
||||
@page-size-change="handleSizeChange"
|
||||
@page-current-change="handleCurrentChange"
|
||||
>
|
||||
<template #operation="{ row }">
|
||||
<el-button :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="openDialog('修改', row)"> 修改 </el-button>
|
||||
<el-popconfirm :title="`是否确认删除用户编号为${row.id}的这条数据`" @confirm="handleDelete(row)">
|
||||
<template #reference>
|
||||
<el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary"> 删除 </el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
<el-dropdown>
|
||||
<el-button :icon="useRenderIcon(More)" :size="size" class="ml-3 mt-[2px]" link type="primary" @click="handleUpdate(row)" />
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>
|
||||
<el-button :class="buttonClass" :icon="useRenderIcon(Upload)" :size="size" link type="primary" @click="handleUpload(row)"> 上传头像 </el-button>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<el-button :class="buttonClass" :icon="useRenderIcon(Password)" :size="size" link type="primary" @click="handleReset(row)"> 重置密码 </el-button>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<el-button :class="buttonClass" :icon="useRenderIcon(Role)" :size="size" link type="primary" @click="handleRole(row)"> 分配角色 </el-button>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</pure-table>
|
||||
</template>
|
||||
</PureTableBar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-dropdown-menu__item i) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:deep(.el-button:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin: 24px 24px 0 !important;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1 +0,0 @@
|
|||
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M22 4V2H2v2h9v14.17l-5.5-5.5-1.42 1.41L12 22l7.92-7.92-1.42-1.41-5.5 5.5V4z"/></svg>
|
Before Width: | Height: | Size: 161 B |
|
@ -1 +0,0 @@
|
|||
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M4 2H2v20h2v-9h14.17l-5.5 5.5 1.41 1.42L22 12l-7.92-7.92-1.41 1.42 5.5 5.5H4z"/></svg>
|
Before Width: | Height: | Size: 163 B |
|
@ -1,156 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { useRenderIcon } from '@/components/ReIcon/src/hooks';
|
||||
import { computed, getCurrentInstance, ref, watch } from 'vue';
|
||||
|
||||
import Dept from '@iconify-icons/ri/git-branch-line';
|
||||
// import Reset from "@iconify-icons/ri/restart-line";
|
||||
import More2Fill from '@iconify-icons/ri/more-2-fill';
|
||||
import OfficeBuilding from '@iconify-icons/ep/office-building';
|
||||
import LocationCompany from '@iconify-icons/ep/add-location';
|
||||
import ExpandIcon from './svg/expand.svg?component';
|
||||
import UnExpandIcon from './svg/unexpand.svg?component';
|
||||
|
||||
interface Tree {
|
||||
id: number;
|
||||
name: string;
|
||||
highlight?: boolean;
|
||||
children?: Tree[];
|
||||
}
|
||||
|
||||
defineProps({
|
||||
treeLoading: Boolean,
|
||||
treeData: Array,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['tree-select']);
|
||||
|
||||
const treeRef = ref();
|
||||
const isExpand = ref(true);
|
||||
const searchValue = ref('');
|
||||
const highlightMap = ref({});
|
||||
const { proxy } = getCurrentInstance();
|
||||
const defaultProps = {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
};
|
||||
const buttonClass = computed(() => {
|
||||
return ['!h-[20px]', '!text-sm', 'reset-margin', '!text-[var(--el-text-color-regular)]', 'dark:!text-white', 'dark:hover:!text-primary'];
|
||||
});
|
||||
|
||||
const filterNode = (value: string, data: Tree) => {
|
||||
if (!value) return true;
|
||||
return data.name.includes(value);
|
||||
};
|
||||
|
||||
function nodeClick(value) {
|
||||
const nodeId = value.$treeNodeId;
|
||||
highlightMap.value[nodeId] = highlightMap.value[nodeId]?.highlight
|
||||
? Object.assign({ id: nodeId }, highlightMap.value[nodeId], {
|
||||
highlight: false,
|
||||
})
|
||||
: Object.assign({ id: nodeId }, highlightMap.value[nodeId], {
|
||||
highlight: true,
|
||||
});
|
||||
Object.values(highlightMap.value).forEach((v: Tree) => {
|
||||
if (v.id !== nodeId) {
|
||||
v.highlight = false;
|
||||
}
|
||||
});
|
||||
emit('tree-select', highlightMap.value[nodeId]?.highlight ? Object.assign({ ...value, selected: true }) : Object.assign({ ...value, selected: false }));
|
||||
}
|
||||
|
||||
function toggleRowExpansionAll(status) {
|
||||
isExpand.value = status;
|
||||
const nodes = (proxy.$refs['treeRef'] as any).store._getAllNodes();
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
nodes[i].expanded = status;
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置部门树状态(选中状态、搜索框值、树初始化) */
|
||||
function onTreeReset() {
|
||||
highlightMap.value = {};
|
||||
searchValue.value = '';
|
||||
toggleRowExpansionAll(true);
|
||||
}
|
||||
|
||||
watch(searchValue, val => {
|
||||
treeRef.value!.filter(val);
|
||||
});
|
||||
|
||||
defineExpose({ onTreeReset });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-loading="treeLoading" :style="{ minHeight: `calc(100vh - 141px)` }" class="h-full bg-bg_color overflow-hidden relative">
|
||||
<div class="flex items-center h-[34px]">
|
||||
<el-input v-model="searchValue" class="ml-2" clearable placeholder="请输入部门名称" size="small">
|
||||
<template #suffix>
|
||||
<el-icon class="el-input__icon">
|
||||
<IconifyIconOffline v-show="searchValue.length === 0" icon="ri:search-line" />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-dropdown :hide-on-click="false">
|
||||
<IconifyIconOffline :icon="More2Fill" class="w-[28px] cursor-pointer" width="18px" />
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>
|
||||
<el-button :class="buttonClass" :icon="useRenderIcon(isExpand ? ExpandIcon : UnExpandIcon)" link type="primary" @click="toggleRowExpansionAll(isExpand ? false : true)">
|
||||
{{ isExpand ? '折叠全部' : '展开全部' }}
|
||||
</el-button>
|
||||
</el-dropdown-item>
|
||||
<!-- <el-dropdown-item>
|
||||
<el-button
|
||||
:class="buttonClass"
|
||||
link
|
||||
type="primary"
|
||||
:icon="useRenderIcon(Reset)"
|
||||
@click="onTreeReset"
|
||||
>
|
||||
重置状态
|
||||
</el-button>
|
||||
</el-dropdown-item> -->
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<el-divider />
|
||||
<el-scrollbar height="calc(90vh - 88px)">
|
||||
<el-tree ref="treeRef" :data="treeData" :expand-on-click-node="false" :filter-node-method="filterNode" :props="defaultProps" default-expand-all node-key="id" size="small" @node-click="nodeClick">
|
||||
<template #default="{ node, data }">
|
||||
<div
|
||||
:class="[
|
||||
'rounded',
|
||||
'flex',
|
||||
'items-center',
|
||||
'select-none',
|
||||
'hover:text-primary',
|
||||
searchValue.trim().length > 0 && node.label.includes(searchValue) && 'text-red-500',
|
||||
highlightMap[node.id]?.highlight ? 'dark:text-primary' : '',
|
||||
]"
|
||||
:style="{
|
||||
color: highlightMap[node.id]?.highlight ? 'var(--el-color-primary)' : '',
|
||||
background: highlightMap[node.id]?.highlight ? 'var(--el-color-primary-light-7)' : 'transparent',
|
||||
}"
|
||||
>
|
||||
<IconifyIconOffline :icon="data.type === 1 ? OfficeBuilding : data.type === 2 ? LocationCompany : Dept" />
|
||||
<span :title="node.label" class="!w-[120px] !truncate">
|
||||
{{ node.label }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-divider) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:deep(.el-tree) {
|
||||
--el-tree-node-hover-bg-color: transparent;
|
||||
}
|
||||
</style>
|
|
@ -1,456 +0,0 @@
|
|||
import './reset.css';
|
||||
import dayjs from 'dayjs';
|
||||
import roleForm from '../form/role.vue';
|
||||
import editForm from '../form/index.vue';
|
||||
import { zxcvbn } from '@zxcvbn-ts/core';
|
||||
import { handleTree } from '@/utils/tree';
|
||||
import { message } from '@/utils/message';
|
||||
import userAvatar from '@/assets/user.jpg';
|
||||
import { usePublicHooks } from '../../hooks';
|
||||
import { addDialog } from '@/components/BaseDialog';
|
||||
import type { PaginationProps } from '@pureadmin/table';
|
||||
import ReCropperPreview from '@/components/ReCropperPreview';
|
||||
import type { FormItemProps, RoleFormItemProps } from '../utils/types';
|
||||
import { deviceDetection, getKeyList, hideTextAtIndex, isAllEmpty } from '@pureadmin/utils';
|
||||
import { getAllRoleList, getDeptList, getRoleIds, getUserList } from '@/api/v1/system';
|
||||
import { ElForm, ElFormItem, ElInput, ElMessageBox, ElProgress } from 'element-plus';
|
||||
import { computed, h, onMounted, reactive, ref, type Ref, toRaw, watch } from 'vue';
|
||||
|
||||
export function useUser(tableRef: Ref, treeRef: Ref) {
|
||||
const form = reactive({
|
||||
// 左侧部门树的id
|
||||
deptId: '',
|
||||
username: '',
|
||||
phone: '',
|
||||
status: '',
|
||||
});
|
||||
const formRef = ref();
|
||||
const ruleFormRef = ref();
|
||||
const dataList = ref([]);
|
||||
const loading = ref(true);
|
||||
// 上传头像信息
|
||||
const avatarInfo = ref();
|
||||
const switchLoadMap = ref({});
|
||||
const { switchStyle } = usePublicHooks();
|
||||
const higherDeptOptions = ref();
|
||||
const treeData = ref([]);
|
||||
const treeLoading = ref(true);
|
||||
const selectedNum = ref(0);
|
||||
const pagination = reactive<PaginationProps>({
|
||||
total: 0,
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
background: true,
|
||||
});
|
||||
const columns: TableColumnList = [
|
||||
{
|
||||
label: '勾选列', // 如果需要表格多选,此处label必须设置
|
||||
type: 'selection',
|
||||
fixed: 'left',
|
||||
reserveSelection: true, // 数据刷新后保留选项
|
||||
},
|
||||
{
|
||||
label: '用户编号',
|
||||
prop: 'id',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
label: '用户头像',
|
||||
prop: 'avatar',
|
||||
cellRenderer: ({ row }) => (
|
||||
<el-image fit='cover' preview-teleported={true} src={row.avatar || userAvatar} preview-src-list={Array.of(row.avatar || userAvatar)} class='w-[24px] h-[24px] rounded-full align-middle' />
|
||||
),
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
label: '用户名称',
|
||||
prop: 'username',
|
||||
minWidth: 130,
|
||||
},
|
||||
{
|
||||
label: '用户昵称',
|
||||
prop: 'nickname',
|
||||
minWidth: 130,
|
||||
},
|
||||
{
|
||||
label: '性别',
|
||||
prop: 'sex',
|
||||
minWidth: 90,
|
||||
cellRenderer: ({ row, props }) => (
|
||||
<el-tag size={props.size} type={row.sex === 1 ? 'danger' : null} effect='plain'>
|
||||
{row.sex === 1 ? '女' : '男'}
|
||||
</el-tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: '部门',
|
||||
prop: 'dept.name',
|
||||
minWidth: 90,
|
||||
},
|
||||
{
|
||||
label: '手机号码',
|
||||
prop: 'phone',
|
||||
minWidth: 90,
|
||||
formatter: ({ phone }) => hideTextAtIndex(phone, { start: 3, end: 6 }),
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
minWidth: 90,
|
||||
cellRenderer: scope => (
|
||||
<el-switch
|
||||
size={scope.props.size === 'small' ? 'small' : 'default'}
|
||||
loading={switchLoadMap.value[scope.index]?.loading}
|
||||
v-model={scope.row.status}
|
||||
active-value={1}
|
||||
inactive-value={0}
|
||||
active-text='已启用'
|
||||
inactive-text='已停用'
|
||||
inline-prompt
|
||||
style={switchStyle.value}
|
||||
onChange={() => onChange(scope as any)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
minWidth: 90,
|
||||
prop: 'createTime',
|
||||
formatter: ({ createTime }) => dayjs(createTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
},
|
||||
{
|
||||
label: '操作',
|
||||
fixed: 'right',
|
||||
width: 180,
|
||||
slot: 'operation',
|
||||
},
|
||||
];
|
||||
const buttonClass = computed(() => {
|
||||
return ['!h-[20px]', 'reset-margin', '!text-gray-500', 'dark:!text-white', 'dark:hover:!text-primary'];
|
||||
});
|
||||
// 重置的新密码
|
||||
const pwdForm = reactive({
|
||||
newPwd: '',
|
||||
});
|
||||
const pwdProgress = [
|
||||
{ color: '#e74242', text: '非常弱' },
|
||||
{ color: '#EFBD47', text: '弱' },
|
||||
{ color: '#ffa500', text: '一般' },
|
||||
{ color: '#1bbf1b', text: '强' },
|
||||
{ color: '#008000', text: '非常强' },
|
||||
];
|
||||
// 当前密码强度(0-4)
|
||||
const curScore = ref();
|
||||
const roleOptions = ref([]);
|
||||
|
||||
function onChange({ row, index }) {
|
||||
ElMessageBox.confirm(`确认要<strong>${row.status === 0 ? '停用' : '启用'}</strong><strong style='color:var(--el-color-primary)'>${row.username}</strong>用户吗?`, '系统提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
dangerouslyUseHTMLString: true,
|
||||
draggable: true,
|
||||
})
|
||||
.then(() => {
|
||||
switchLoadMap.value[index] = Object.assign({}, switchLoadMap.value[index], {
|
||||
loading: true,
|
||||
});
|
||||
setTimeout(() => {
|
||||
switchLoadMap.value[index] = Object.assign({}, switchLoadMap.value[index], {
|
||||
loading: false,
|
||||
});
|
||||
message('已成功修改用户状态', {
|
||||
type: 'success',
|
||||
});
|
||||
}, 300);
|
||||
})
|
||||
.catch(() => {
|
||||
row.status === 0 ? (row.status = 1) : (row.status = 0);
|
||||
});
|
||||
}
|
||||
|
||||
function handleUpdate(row) {
|
||||
console.log(row);
|
||||
}
|
||||
|
||||
function handleDelete(row) {
|
||||
message(`您删除了用户编号为${row.id}的这条数据`, { type: 'success' });
|
||||
onSearch();
|
||||
}
|
||||
|
||||
function handleSizeChange(val: number) {
|
||||
console.log(`${val} items per page`);
|
||||
}
|
||||
|
||||
function handleCurrentChange(val: number) {
|
||||
console.log(`current page: ${val}`);
|
||||
}
|
||||
|
||||
/** 当CheckBox选择项发生变化时会触发该事件 */
|
||||
function handleSelectionChange(val) {
|
||||
selectedNum.value = val.length;
|
||||
// 重置表格高度
|
||||
tableRef.value.setAdaptive();
|
||||
}
|
||||
|
||||
/** 取消选择 */
|
||||
function onSelectionCancel() {
|
||||
selectedNum.value = 0;
|
||||
// 用于多选表格,清空用户的选择
|
||||
tableRef.value.getTableRef().clearSelection();
|
||||
}
|
||||
|
||||
/** 批量删除 */
|
||||
function onbatchDel() {
|
||||
// 返回当前选中的行
|
||||
const curSelected = tableRef.value.getTableRef().getSelectionRows();
|
||||
// 接下来根据实际业务,通过选中行的某项数据,比如下面的id,调用接口进行批量删除
|
||||
message(`已删除用户编号为 ${getKeyList(curSelected, 'id')} 的数据`, {
|
||||
type: 'success',
|
||||
});
|
||||
tableRef.value.getTableRef().clearSelection();
|
||||
onSearch();
|
||||
}
|
||||
|
||||
async function onSearch() {
|
||||
loading.value = true;
|
||||
const { data } = await getUserList(toRaw(form));
|
||||
dataList.value = data.list;
|
||||
pagination.total = data.total;
|
||||
pagination.pageSize = data.pageSize;
|
||||
pagination.currentPage = data.currentPage;
|
||||
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
const resetForm = formEl => {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
form.deptId = '';
|
||||
treeRef.value.onTreeReset();
|
||||
onSearch();
|
||||
};
|
||||
|
||||
function onTreeSelect({ id, selected }) {
|
||||
form.deptId = selected ? id : '';
|
||||
onSearch();
|
||||
}
|
||||
|
||||
function formatHigherDeptOptions(treeList) {
|
||||
// 根据返回数据的status字段值判断追加是否禁用disabled字段,返回处理后的树结构,用于上级部门级联选择器的展示(实际开发中也是如此,不可能前端需要的每个字段后端都会返回,这时需要前端自行根据后端返回的某些字段做逻辑处理)
|
||||
if (!treeList || !treeList.length) return;
|
||||
const newTreeList = [];
|
||||
for (let i = 0; i < treeList.length; i++) {
|
||||
treeList[i].disabled = treeList[i].status === 0 ? true : false;
|
||||
formatHigherDeptOptions(treeList[i].children);
|
||||
newTreeList.push(treeList[i]);
|
||||
}
|
||||
return newTreeList;
|
||||
}
|
||||
|
||||
function openDialog(title = '新增', row?: FormItemProps) {
|
||||
addDialog({
|
||||
title: `${title}用户`,
|
||||
props: {
|
||||
formInline: {
|
||||
title,
|
||||
higherDeptOptions: formatHigherDeptOptions(higherDeptOptions.value),
|
||||
parentId: row?.dept.id ?? 0,
|
||||
nickname: row?.nickname ?? '',
|
||||
username: row?.username ?? '',
|
||||
password: row?.password ?? '',
|
||||
phone: row?.phone ?? '',
|
||||
email: row?.email ?? '',
|
||||
sex: row?.sex ?? '',
|
||||
status: row?.status ?? 1,
|
||||
remark: row?.remark ?? '',
|
||||
},
|
||||
},
|
||||
width: '46%',
|
||||
draggable: true,
|
||||
fullscreen: deviceDetection(),
|
||||
fullscreenIcon: true,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () => h(editForm, { ref: formRef, formInline: null }),
|
||||
beforeSure: (done, { options }) => {
|
||||
const FormRef = formRef.value.getRef();
|
||||
const curData = options.props.formInline as FormItemProps;
|
||||
|
||||
function chores() {
|
||||
message(`您${title}了用户名称为${curData.username}的这条数据`, {
|
||||
type: 'success',
|
||||
});
|
||||
done(); // 关闭弹框
|
||||
onSearch(); // 刷新表格数据
|
||||
}
|
||||
|
||||
FormRef.validate(valid => {
|
||||
if (valid) {
|
||||
console.log('curData', curData);
|
||||
// 表单规则校验通过
|
||||
if (title === '新增') {
|
||||
// 实际开发先调用新增接口,再进行下面操作
|
||||
chores();
|
||||
} else {
|
||||
// 实际开发先调用修改接口,再进行下面操作
|
||||
chores();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const cropRef = ref();
|
||||
|
||||
/** 上传头像 */
|
||||
function handleUpload(row) {
|
||||
addDialog({
|
||||
title: '裁剪、上传头像',
|
||||
width: '40%',
|
||||
closeOnClickModal: false,
|
||||
fullscreen: deviceDetection(),
|
||||
contentRenderer: () =>
|
||||
h(ReCropperPreview, {
|
||||
ref: cropRef,
|
||||
imgSrc: row.avatar || userAvatar,
|
||||
onCropper: info => (avatarInfo.value = info),
|
||||
}),
|
||||
beforeSure: done => {
|
||||
console.log('裁剪后的图片信息:', avatarInfo.value);
|
||||
// 根据实际业务使用avatarInfo.value和row里的某些字段去调用上传头像接口即可
|
||||
done(); // 关闭弹框
|
||||
onSearch(); // 刷新表格数据
|
||||
},
|
||||
closeCallBack: () => cropRef.value.hidePopover(),
|
||||
});
|
||||
}
|
||||
|
||||
watch(pwdForm, ({ newPwd }) => (curScore.value = isAllEmpty(newPwd) ? -1 : zxcvbn(newPwd).score));
|
||||
|
||||
/** 重置密码 */
|
||||
function handleReset(row) {
|
||||
addDialog({
|
||||
title: `重置 ${row.username} 用户的密码`,
|
||||
width: '30%',
|
||||
draggable: true,
|
||||
closeOnClickModal: false,
|
||||
fullscreen: deviceDetection(),
|
||||
contentRenderer: () => (
|
||||
<>
|
||||
<ElForm ref={ruleFormRef} model={pwdForm}>
|
||||
<ElFormItem
|
||||
prop='newPwd'
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入新密码',
|
||||
trigger: 'blur',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ElInput clearable show-password type='password' v-model={pwdForm.newPwd} placeholder='请输入新密码' />
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<div class='mt-4 flex'>
|
||||
{pwdProgress.map(({ color, text }, idx) => (
|
||||
<div class='w-[19vw]' style={{ marginLeft: idx !== 0 ? '4px' : 0 }}>
|
||||
<ElProgress striped striped-flow duration={curScore.value === idx ? 6 : 0} percentage={curScore.value >= idx ? 100 : 0} color={color} stroke-width={10} show-text={false} />
|
||||
<p class='text-center' style={{ color: curScore.value === idx ? color : '' }}>
|
||||
{text}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
closeCallBack: () => (pwdForm.newPwd = ''),
|
||||
beforeSure: done => {
|
||||
ruleFormRef.value.validate(valid => {
|
||||
if (valid) {
|
||||
// 表单规则校验通过
|
||||
message(`已成功重置 ${row.username} 用户的密码`, {
|
||||
type: 'success',
|
||||
});
|
||||
console.log(pwdForm.newPwd);
|
||||
// 根据实际业务使用pwdForm.newPwd和row里的某些字段去调用重置用户密码接口即可
|
||||
done(); // 关闭弹框
|
||||
onSearch(); // 刷新表格数据
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** 分配角色 */
|
||||
async function handleRole(row) {
|
||||
// 选中的角色列表
|
||||
const ids = (await getRoleIds({ userId: row.id })).data ?? [];
|
||||
addDialog({
|
||||
title: `分配 ${row.username} 用户的角色`,
|
||||
props: {
|
||||
formInline: {
|
||||
username: row?.username ?? '',
|
||||
nickname: row?.nickname ?? '',
|
||||
roleOptions: roleOptions.value ?? [],
|
||||
ids,
|
||||
},
|
||||
},
|
||||
width: '400px',
|
||||
draggable: true,
|
||||
fullscreen: deviceDetection(),
|
||||
fullscreenIcon: true,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () => h(roleForm),
|
||||
beforeSure: (done, { options }) => {
|
||||
const curData = options.props.formInline as RoleFormItemProps;
|
||||
console.log('curIds', curData.ids);
|
||||
// 根据实际业务使用curData.ids和row里的某些字段去调用修改角色接口即可
|
||||
done(); // 关闭弹框
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
treeLoading.value = true;
|
||||
onSearch();
|
||||
|
||||
// 归属部门
|
||||
const { data } = await getDeptList();
|
||||
higherDeptOptions.value = handleTree(data);
|
||||
treeData.value = handleTree(data);
|
||||
treeLoading.value = false;
|
||||
|
||||
// 角色列表
|
||||
roleOptions.value = (await getAllRoleList()).data;
|
||||
});
|
||||
|
||||
return {
|
||||
form,
|
||||
loading,
|
||||
columns,
|
||||
dataList,
|
||||
treeData,
|
||||
treeLoading,
|
||||
selectedNum,
|
||||
pagination,
|
||||
buttonClass,
|
||||
deviceDetection,
|
||||
onSearch,
|
||||
resetForm,
|
||||
onbatchDel,
|
||||
openDialog,
|
||||
onTreeSelect,
|
||||
handleUpdate,
|
||||
handleDelete,
|
||||
handleUpload,
|
||||
handleReset,
|
||||
handleRole,
|
||||
handleSizeChange,
|
||||
onSelectionCancel,
|
||||
handleCurrentChange,
|
||||
handleSelectionChange,
|
||||
};
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
/** 局部重置 ElProgress 的部分样式 */
|
||||
.el-progress-bar__outer,
|
||||
.el-progress-bar__inner {
|
||||
border-radius: 0;
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
import { reactive } from 'vue';
|
||||
import type { FormRules } from 'element-plus';
|
||||
import { isEmail, isPhone } from '@pureadmin/utils';
|
||||
|
||||
/** 自定义表单规则校验 */
|
||||
export const formRules = reactive(<FormRules>{
|
||||
nickname: [{ required: true, message: '用户昵称为必填项', trigger: 'blur' }],
|
||||
username: [{ required: true, message: '用户名称为必填项', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '用户密码为必填项', trigger: 'blur' }],
|
||||
phone: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === '') {
|
||||
callback();
|
||||
} else if (!isPhone(value)) {
|
||||
callback(new Error('请输入正确的手机号码格式'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'blur',
|
||||
// trigger: "click" // 如果想在点击确定按钮时触发这个校验,trigger 设置成 click 即可
|
||||
},
|
||||
],
|
||||
email: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === '') {
|
||||
callback();
|
||||
} else if (!isEmail(value)) {
|
||||
callback(new Error('请输入正确的邮箱格式'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
});
|
|
@ -1,38 +0,0 @@
|
|||
interface FormItemProps {
|
||||
id?: number;
|
||||
/** 用于判断是`新增`还是`修改` */
|
||||
title: string;
|
||||
higherDeptOptions: Record<string, unknown>[];
|
||||
parentId: number;
|
||||
nickname: string;
|
||||
username: string;
|
||||
password: string;
|
||||
phone: string | number;
|
||||
email: string;
|
||||
sex: string | number;
|
||||
status: number;
|
||||
dept?: {
|
||||
id?: number;
|
||||
name?: string;
|
||||
};
|
||||
remark: string;
|
||||
}
|
||||
|
||||
interface FormProps {
|
||||
formInline: FormItemProps;
|
||||
}
|
||||
|
||||
interface RoleFormItemProps {
|
||||
username: string;
|
||||
nickname: string;
|
||||
/** 角色列表 */
|
||||
roleOptions: any[];
|
||||
/** 选中的角色列表 */
|
||||
ids: Record<number, unknown>[];
|
||||
}
|
||||
|
||||
interface RoleFormProps {
|
||||
formInline: RoleFormItemProps;
|
||||
}
|
||||
|
||||
export type { FormItemProps, FormProps, RoleFormItemProps, RoleFormProps };
|
Loading…
Reference in New Issue