completepage: 🍻 多语言页面完成
|
@ -130,7 +130,7 @@ class PureHttp {
|
|||
message(data.message, { type: 'warning' });
|
||||
router.push('/').then();
|
||||
removeToken();
|
||||
} else if (data.code >= 209 && data.code < 300) {
|
||||
} else if (data.code >= 201 && data.code < 300) {
|
||||
message(data.message, { type: 'warning' });
|
||||
} else if (data.code > 300) {
|
||||
message(data.message, { type: 'error' });
|
||||
|
|
|
@ -33,7 +33,7 @@ export const fetchUpdateI18n = (data: any) => {
|
|||
* 多语言管理---删除多语言
|
||||
*/
|
||||
export const fetchDeleteI18n = (data: any) => {
|
||||
return http.request<BaseResult<object>>('put', 'i18n/deleteI18n', { data });
|
||||
return http.request<BaseResult<object>>('delete', 'i18n/deleteI18n', { data });
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -56,3 +56,10 @@ export const refreshTokenApi = (data?: object) => {
|
|||
export const fetchLogout = (data?: object) => {
|
||||
return http.request<BaseResult<any>>('post', 'user/logout', { data });
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
export const fetchGetUserinfoById = (data?: object) => {
|
||||
return http.request<BaseResult<UserResult>>('get', 'user/getUserinfoById', { params: data });
|
||||
};
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg width="32" height="32" viewBox="0 0 48 48"><path fill="#2F88FF" fill-rule="evenodd" stroke="#000" stroke-linejoin="round" stroke-width="4" d="M44 40.836q-7.34-8.96-13.036-10.168t-10.846-.365V41L4 23.545 20.118 7v10.167q9.523.075 16.192 6.833 6.668 6.758 7.69 16.836Z" clip-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 300 B |
|
@ -0,0 +1 @@
|
|||
<svg width="1em" height="1em" fill="none" class="t-icon t-icon-calendar" viewBox="0 0 16 16"><path fill="currentColor" d="M10 3H6V1.5H5V3H3a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1h-2V1.5h-1zM5 5h1V4h4v1h1V4h2v2H3V4h2zM3 7h10v6H3z"/></svg>
|
After Width: | Height: | Size: 261 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 1024 1024"><path fill="#FF5D50" d="M428.698 107.315c-6.503 72.192-36.352 207.258-160.256 337.408 3.686-48.025-7.117-83.763-19.047-107.673-6.605-13.159-26.06-10.599-28.877 3.84-5.734 29.44-20.582 75.059-57.6 137.779-71.628 121.395-62.566 459.878 340.736 459.878S934.093 585.728 876.8 442.522c-37.376-93.44-93.952-152.525-128.82-182.324-11.417-9.779-29.132-1.945-29.593 13.056-.921 30.464-7.321 73.37-33.075 102.144-.666-52.787-38.144-208.384-202.445-296.857-23.296-12.544-51.763 2.457-54.17 28.774z"/><path fill="#FFDF99" d="M702.26 678.4c-4.2-45.056-60.673-166.554-212.634-246.426-10.599-5.58-23.092 3.124-21.504 15.002 6.246 46.848 12.953 140.493-24.064 184.73 4.044-40.397-18.125-73.83-36.66-94.31-8.396-9.217-23.552-4.66-25.497 7.68-3.533 22.322-12.851 56.268-36.557 97.945-42.086 74.035-86.989 188.672 124.57 294.656 10.956.563 22.17.87 33.74.87a618 618 0 0 0 32.717-.87C694.631 878.182 709.837 759.706 702.26 678.4"/></svg>
|
After Width: | Height: | Size: 1004 B |
|
@ -0,0 +1 @@
|
|||
<svg width="1em" height="1em" fill="none" class="t-icon t-icon-laptop" viewBox="0 0 16 16"><path fill="currentColor" d="M2.5 12a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h11a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1zm0-1h11V4h-11zM15 13H1v1h14z"/></svg>
|
After Width: | Height: | Size: 228 B |
|
@ -0,0 +1 @@
|
|||
<svg width="1em" height="1em" fill="none" class="t-icon t-icon-service" viewBox="0 0 16 16"><path fill="currentColor" d="M2.52 6.37a5.5 5.5 0 0 1 10.98.13v4c0 .05 0 .1-.02.15A4.5 4.5 0 0 1 9 14.7H8v-1h1a3.5 3.5 0 0 0 3.4-2.7h-1.9a.5.5 0 0 1-.5-.5v-4c0-.28.22-.5.5-.5h1.93a4.5 4.5 0 0 0-8.86 0H5.5c.28 0 .5.22.5.5v4a.5.5 0 0 1-.5.5H3a.5.5 0 0 1-.5-.5v-4c0-.04 0-.09.02-.13M12.5 7H11v3h1.5zm-9 0v3H5V7z"/></svg>
|
After Width: | Height: | Size: 409 B |
|
@ -0,0 +1 @@
|
|||
<svg width="1em" height="1em" fill="none" class="t-icon t-icon-shop" viewBox="0 0 16 16"><path fill="currentColor" d="M8 1a2.5 2.5 0 0 0-2.5 2.5V5h-2a.5.5 0 0 0-.5.5v9c0 .28.22.5.5.5h9a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5h-2V3.5A2.5 2.5 0 0 0 8 1m1.5 5v2h1V6H12v8H4V6h1.5v2h1V6zm0-1h-3V3.5a1.5 1.5 0 1 1 3 0z"/></svg>
|
After Width: | Height: | Size: 317 B |
|
@ -0,0 +1 @@
|
|||
<svg width="1em" height="1em" fill="none" class="t-icon t-icon-user-avatar" viewBox="0 0 16 16"><path fill="currentColor" d="M8 10.5c1.24 0 2.42.31 3.5.88v1.12h1v-1.14a.94.94 0 0 0-.49-.84 8.48 8.48 0 0 0-8.02 0 .94.94 0 0 0-.49.84v1.14h1v-1.12A7.5 7.5 0 0 1 8 10.5M10.5 6a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0m-1 0a1.5 1.5 0 1 0-3 0 1.5 1.5 0 0 0 3 0"/><path fill="currentColor" d="M2.5 1.5a1 1 0 0 0-1 1v11a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1v-11a1 1 0 0 0-1-1zm11 1v11h-11v-11z"/></svg>
|
After Width: | Height: | Size: 482 B |
|
@ -1,161 +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';
|
||||
import { inputValue } from '@/components/ReIcon/src/hooks';
|
||||
|
||||
type ParameterCSSProperties = (item?: string) => CSSProperties | undefined;
|
||||
|
||||
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 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 = '';
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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>
|
||||
<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>
|
||||
|
||||
<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);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,23 +1,183 @@
|
|||
<script lang="ts" setup>
|
||||
import LocalSelect from '@/components/ReIcon/src/LocalSelect.vue';
|
||||
import { inputValue } from '@/components/ReIcon/src/hooks';
|
||||
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>
|
||||
<LocalSelect />
|
||||
<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;
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import type { iconType } from './types';
|
||||
import { type Component, defineComponent, h, ref } from 'vue';
|
||||
import { FontIcon, IconifyIconOffline, IconifyIconOnline } from '../index';
|
||||
|
||||
export const inputValue = ref();
|
||||
import type { iconType } from "./types";
|
||||
import { h, defineComponent, type Component } from "vue";
|
||||
import { IconifyIconOnline, IconifyIconOffline, FontIcon } from "../index";
|
||||
|
||||
/**
|
||||
* 支持 `iconfont`、自定义 `svg` 以及 `iconify` 中所有的图标
|
||||
|
@ -12,48 +10,52 @@ export const inputValue = ref();
|
|||
* @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
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
image: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-image :initial-index="index" :preview-src-list="[image]" :src="image" class="w-[50px] h-[50px]" fit="fill" loading="lazy" preview-teleported />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,14 @@
|
|||
<script lang="ts" setup>
|
||||
import { $t } from '@/plugins/i18n';
|
||||
|
||||
defineProps({
|
||||
status: {
|
||||
type: Boolean,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-tag v-show="!status" effect="dark" size="large" type="success">{{ $t('status.enable') }}</el-tag>
|
||||
<el-tag v-show="status" effect="dark" size="large" type="danger">{{ $t('status.disable') }}</el-tag>
|
||||
</template>
|
|
@ -0,0 +1,134 @@
|
|||
<script lang="ts" setup>
|
||||
import userAvatarIcon from '@/assets/svg/user_avatar.svg?component';
|
||||
import { columns } from './columns';
|
||||
import TablePlus from '@/components/TableBar/src/TablePlus.vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import TableImage from '@/components/Table/TableImage.vue';
|
||||
import { fetchGetUserinfoById } from '@/api/v1/user';
|
||||
import { $t } from '@/plugins/i18n';
|
||||
|
||||
const props = defineProps({
|
||||
userId: { type: String as PropType<String> },
|
||||
});
|
||||
const userinfo = ref();
|
||||
const loading = ref(false);
|
||||
|
||||
/**
|
||||
* * 获取用户信息
|
||||
*/
|
||||
const getUserInfo = async () => {
|
||||
// 如果没有传入用户ID直接返回
|
||||
if (!props.userId) return;
|
||||
loading.value = true;
|
||||
|
||||
// 判断是否是web端
|
||||
const result = await fetchGetUserinfoById({ id: props.userId });
|
||||
if (result.code === 200) {
|
||||
userinfo.value = result.data;
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getUserInfo();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="list-card-item">
|
||||
<div v-if="userId && userinfo" class="list-card-item_detail bg-bg_color">
|
||||
<el-row justify="space-between">
|
||||
<div class="list-card-item_detail--logo">
|
||||
<userAvatarIcon />
|
||||
</div>
|
||||
<el-tag :color="userinfo.status ? '#F67676' : '#00a870'" class="mx-1 list-card-item_detail--operation--tag" effect="dark">
|
||||
{{ $t('user_status') }}:{{ userinfo.status ? $t('disable') : $t('normal') }}
|
||||
</el-tag>
|
||||
</el-row>
|
||||
|
||||
<p class="list-card-item_detail--name text-text_color_primary">{{ $t('user_details') }}</p>
|
||||
|
||||
<TablePlus :column="columns" :data-list="[userinfo]" :loading="loading">
|
||||
<template #avatar>
|
||||
<table-image :image="userinfo.avatar" />
|
||||
</template>
|
||||
</TablePlus>
|
||||
</div>
|
||||
<el-empty v-else description="无数据" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list-card-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 12px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
|
||||
&_detail {
|
||||
flex: 1;
|
||||
min-height: 140px;
|
||||
padding: 24px 32px;
|
||||
|
||||
&--logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
font-size: 26px;
|
||||
color: #0052d9;
|
||||
background: #e0ebff;
|
||||
border-radius: 50%;
|
||||
|
||||
&__disabled {
|
||||
color: #a1c4ff;
|
||||
}
|
||||
}
|
||||
|
||||
&--operation {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
||||
&--tag {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&--name {
|
||||
margin: 24px 0 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&--desc {
|
||||
display: -webkit-box;
|
||||
height: 40px;
|
||||
margin-bottom: 24px;
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
&--desc {
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
}
|
||||
|
||||
&__disabled {
|
||||
.list-card-item_detail--name,
|
||||
.list-card-item_detail--desc {
|
||||
color: var(--el-text-color-disabled);
|
||||
}
|
||||
|
||||
.list-card-item_detail--operation--tag {
|
||||
color: #bababa;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,29 @@
|
|||
import { $t } from '@/plugins/i18n';
|
||||
import UserinfoDialog from '@/components/Table/Userinfo/UserinfoDialog.vue';
|
||||
import { addDialog } from '@/components/BaseDialog/index'; // 表格列字段
|
||||
|
||||
// 表格列字段
|
||||
export const columns = [
|
||||
{ label: $t('id'), prop: 'id' },
|
||||
{ label: $t('avatar'), prop: 'avatar', slot: 'avatar' },
|
||||
{ label: $t('nickName'), prop: 'nickName' },
|
||||
{ label: $t('username'), prop: 'username' },
|
||||
{ label: $t('email'), prop: 'email', width: 180 },
|
||||
{ label: $t('phone'), prop: 'phone', width: 180 },
|
||||
{ label: $t('sex'), prop: 'sex' },
|
||||
{ label: $t('personDescription'), prop: 'personDescription', width: 180 },
|
||||
{ label: $t('table.createTime'), prop: 'createTime', width: '160' },
|
||||
{ label: $t('table.updateTime'), prop: 'updateTime', width: '160' },
|
||||
];
|
||||
|
||||
/**
|
||||
* * 查看用户信息
|
||||
* @param userId
|
||||
*/
|
||||
export const selectUserinfo = async (userId: string) => {
|
||||
addDialog({
|
||||
title: '查看用户信息',
|
||||
draggable: true,
|
||||
contentRenderer: (): JSX.Element => <UserinfoDialog userId={userId} />,
|
||||
});
|
||||
};
|
|
@ -1,13 +1,29 @@
|
|||
import { defineStore } from 'pinia';
|
||||
import { addMenu, deletedMenuByIds, updateMenu } from '@/api/v1/system';
|
||||
import { addMenu, deletedMenuByIds, getMenuList, updateMenu } from '@/api/v1/system';
|
||||
import { storeMessage } from '@/utils/message';
|
||||
import { handleTree } from '@/utils/tree';
|
||||
|
||||
export const userRouterStore = defineStore('routerStore', {
|
||||
state() {
|
||||
return {};
|
||||
return {
|
||||
datalist: [],
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
getters: {},
|
||||
actions: {
|
||||
/**
|
||||
* * 获取菜单列表
|
||||
*/
|
||||
async getMenuList() {
|
||||
const result = await getMenuList();
|
||||
if (result.code === 200) {
|
||||
this.datalist = handleTree(result.data as any);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* * 添加菜单
|
||||
* @param data
|
||||
|
|
|
@ -95,7 +95,6 @@ export const closeAllMessage = (): void => ElMessage.closeAll();
|
|||
*/
|
||||
export const storeMessage = (result: BaseResult<any>) => {
|
||||
if (result.code !== 200) {
|
||||
message(result.message, { type: 'warning' });
|
||||
return false;
|
||||
}
|
||||
message(result.message, { type: 'success' });
|
||||
|
|
|
@ -27,7 +27,7 @@ defineExpose({ ruleFormRef });
|
|||
<el-form ref="ruleFormRef" :model="form" :rules="rules" isDefault-icon label-position="left" label-width="135px">
|
||||
<el-form-item label="选择添加语言分类" prop="typeName">
|
||||
<el-select v-model="form.typeName" filterable placeholder="选择添加语言分类">
|
||||
<el-option v-for="item in i18nTypeStore.datalist" :key="item.typeName" :label="item.typeName" :value="item.typeName" />
|
||||
<el-option v-for="item in i18nTypeStore.datalist" :key="item.id" :label="item.typeName" :value="item.typeName" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import PureTable from '@pureadmin/table';
|
|||
import { columns } from '@/views/i18n/i18n-setting/utils/columns';
|
||||
import Refresh from '@iconify-icons/ep/refresh';
|
||||
import { $t } from '@/plugins/i18n';
|
||||
import { selectUserinfo } from '@/components/Table/Userinfo/columns';
|
||||
|
||||
const tableRef = ref();
|
||||
const pageFormRef = ref();
|
||||
|
@ -43,14 +44,6 @@ const onPageSizeChange = async (value: number) => {
|
|||
await onSearch();
|
||||
};
|
||||
|
||||
/**
|
||||
* * 选择框点击的行
|
||||
* @param row
|
||||
*/
|
||||
const onSelectionChange = (row: any) => {
|
||||
ids.value = row.map((item: any) => item.id);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
onSearch();
|
||||
});
|
||||
|
@ -95,10 +88,17 @@ onMounted(() => {
|
|||
row-key="id"
|
||||
showOverflowTooltip
|
||||
table-layout="auto"
|
||||
@selection-change="onSelectionChange"
|
||||
@page-size-change="onPageSizeChange"
|
||||
@page-current-change="onCurrentPageChange"
|
||||
>
|
||||
<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 v-show="row.menuType !== 3" :icon="useRenderIcon(AddFill)" :size="size" class="reset-margin" link type="primary"> {{ $t('add_new') }} </el-button>
|
||||
|
|
|
@ -10,8 +10,8 @@ export const columns: TableColumnList = [
|
|||
{ label: $t('i18n.typeName'), prop: 'typeName' },
|
||||
{ label: $t('table.updateTime'), prop: 'updateTime' },
|
||||
{ label: $t('table.createTime'), prop: 'createTime' },
|
||||
{ label: $t('table.createUser'), prop: 'createUser' },
|
||||
{ label: $t('table.updateUser'), prop: 'updateUser' },
|
||||
{ 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' },
|
||||
];
|
||||
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import { h, ref } from 'vue';
|
||||
import { userI18nStore } from '@/store/i18n/i18n';
|
||||
import { messageBox } from '@/utils/message';
|
||||
import { addDialog, closeDialog } from '@/components/BaseDialog/index';
|
||||
import { deviceDetection } from '@pureadmin/utils';
|
||||
import I18nDialog from '@/views/i18n/i18n-setting/i18n-dialog.vue';
|
||||
import type { FormProps } from '@/views/i18n/i18n-setting/utils/types';
|
||||
import { $t } from '@/plugins/i18n';
|
||||
import { messageBox } from '@/utils/message';
|
||||
|
||||
export const formRef = ref();
|
||||
const i18nStore = userI18nStore();
|
||||
export const ids = ref<string[]>([]);
|
||||
|
||||
/**
|
||||
* * 查询内容
|
||||
|
@ -26,7 +25,7 @@ export const onSearch = async () => {
|
|||
*/
|
||||
export const onAdd = () => {
|
||||
addDialog({
|
||||
title: `${$t('add_multilingual')}`,
|
||||
title: $t('add_multilingual'),
|
||||
width: '30%',
|
||||
props: { formInline: { keyName: '', translation: '', typeName: '' } },
|
||||
draggable: true,
|
||||
|
@ -36,7 +35,7 @@ export const onAdd = () => {
|
|||
contentRenderer: () => h(I18nDialog, { ref: formRef }),
|
||||
footerButtons: [
|
||||
{
|
||||
label: '取消',
|
||||
label: $t('cancel'),
|
||||
text: true,
|
||||
bg: true,
|
||||
btnClick: ({ dialog: { options, index } }) => {
|
||||
|
@ -61,7 +60,7 @@ export const onAdd = () => {
|
|||
},
|
||||
},
|
||||
{
|
||||
label: '继续添加',
|
||||
label: $t('continue_adding'),
|
||||
type: 'success',
|
||||
text: true,
|
||||
bg: true,
|
||||
|
@ -86,9 +85,9 @@ export const onUpdate = (row: any) => {
|
|||
const id = row.id;
|
||||
|
||||
addDialog({
|
||||
title: `更新多语言`,
|
||||
title: $t('update_multilingual'),
|
||||
width: '30%',
|
||||
props: { formInline: { keyName: row.keyName, translation: row.translation, typeId: row.typeId } },
|
||||
props: { formInline: { keyName: row.keyName, translation: row.translation, typeName: row.typeName } },
|
||||
draggable: true,
|
||||
fullscreen: deviceDetection(),
|
||||
fullscreenIcon: true,
|
||||
|
@ -107,22 +106,20 @@ export const onUpdate = (row: any) => {
|
|||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* * 批量彻底删除行
|
||||
*/
|
||||
export const onDelete = async () => {
|
||||
export const onDelete = async (row: any) => {
|
||||
const isConfirm = await messageBox({
|
||||
message: '是否确认批量删除(此操作不可逆)',
|
||||
title: '删除警告',
|
||||
message: $t('confirm_delete'),
|
||||
title: $t('delete_warning'),
|
||||
showMessage: false,
|
||||
confirmMessage: '删除成功',
|
||||
cancelMessage: '取消删除',
|
||||
confirmMessage: $t('delete_success'),
|
||||
cancelMessage: $t('cancel_delete'),
|
||||
});
|
||||
|
||||
if (isConfirm) {
|
||||
const data = ids.value;
|
||||
await i18nStore.deleteI18n(data);
|
||||
await i18nStore.deleteI18n([row.id]);
|
||||
await onSearch();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -12,6 +12,8 @@ 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';
|
||||
|
||||
const tableRef = ref();
|
||||
const formRef = ref();
|
||||
|
@ -59,6 +61,8 @@ onMounted(() => {
|
|||
:size="size"
|
||||
adaptive
|
||||
align-whole="center"
|
||||
border
|
||||
highlight-current-row
|
||||
row-key="id"
|
||||
showOverflowTooltip
|
||||
table-layout="auto"
|
||||
|
@ -67,6 +71,14 @@ onMounted(() => {
|
|||
<TableIsDefaultTag :status="row.isDefault" />
|
||||
</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)"> 修改 </el-button>
|
||||
<el-button :icon="useRenderIcon(AddFill)" :size="size" class="reset-margin" link type="primary" @click="onAdd"> 新增 </el-button>
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
import { reactive, ref } from 'vue';
|
||||
import { $t } from '@/plugins/i18n';
|
||||
|
||||
export const editMap = ref({});
|
||||
|
||||
export const columns: TableColumnList = [
|
||||
{ label: 'id', prop: 'id' },
|
||||
{ label: 'i18n_languageName', prop: 'typeName' },
|
||||
{ label: 'i18n_summary', prop: 'summary' },
|
||||
{ label: 'isDefault', prop: 'isDefault', slot: 'isDefault' },
|
||||
{ label: 'updateTime', prop: 'updateTime' },
|
||||
{ label: 'createTime', prop: 'createTime' },
|
||||
{ label: 'createUser', prop: 'createUser' },
|
||||
{ label: 'updateUser', prop: 'updateUser' },
|
||||
{ label: 'operation', fixed: 'right', width: 210, slot: 'operation' },
|
||||
{ label: $t('id'), prop: 'id' },
|
||||
{ label: $t('i18n_typeName'), prop: 'typeName' },
|
||||
{ label: $t('i18n_summary'), prop: 'summary' },
|
||||
{ label: $t('isDefault'), prop: 'isDefault', slot: 'isDefault' },
|
||||
{ label: $t('table.updateTime'), prop: 'updateTime' },
|
||||
{ label: $t('table.createTime'), prop: 'createTime' },
|
||||
{ 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({
|
||||
typeName: [{ required: true, message: '填写语言名称(例如:zh)', trigger: 'blur' }],
|
||||
summary: [{ required: true, message: '填写语言详情', trigger: 'blur' }],
|
||||
typeName: [{ required: true, message: `${$t('input')}${$t('i18n_typeName')}`, trigger: 'blur' }],
|
||||
summary: [{ required: true, message: `${$t('input')}${$t('i18n_summary')}`, trigger: 'blur' }],
|
||||
});
|
||||
|
|
|
@ -3,8 +3,8 @@ import { addDialog } from '@/components/BaseDialog/index';
|
|||
import AddI18nType from '@/views/i18n/i18n-type-setting/i18n-type-dialog.vue';
|
||||
import { userI18nTypeStore } from '@/store/i18n/i18nType';
|
||||
import { h, ref } from 'vue';
|
||||
import type { AddFormItemProps } from '@/views/i18n/i18n-type-setting/utils/types';
|
||||
import { messageBox } from '@/utils/message';
|
||||
import type { FormItemProps } from '@/views/i18n/i18n-type-setting/utils/types';
|
||||
|
||||
export const formRef = ref();
|
||||
const i18nTypeStore = userI18nTypeStore();
|
||||
|
@ -34,7 +34,7 @@ export function onAdd() {
|
|||
closeOnClickModal: false,
|
||||
contentRenderer: () => h(AddI18nType, { ref: formRef }),
|
||||
beforeSure: (done, { options }) => {
|
||||
const form = options.props.formInline as AddFormItemProps;
|
||||
const form = options.props.formInline as FormItemProps;
|
||||
formRef.value.formRef.validate(async (valid: any) => {
|
||||
if (!valid) return;
|
||||
|
||||
|
@ -62,7 +62,7 @@ export function onUpdate(row: any) {
|
|||
closeOnClickModal: false,
|
||||
contentRenderer: () => h(AddI18nType, { ref: formRef }),
|
||||
beforeSure: (done, { options }) => {
|
||||
const form = options.props.formInline as AddFormItemProps;
|
||||
const form = options.props.formInline as FormItemProps;
|
||||
formRef.value.formRef.validate(async (valid: any) => {
|
||||
if (!valid) return;
|
||||
|
||||
|
|
|
@ -5,33 +5,11 @@ import { formRules } from '@/views/system/menu/utils/rule';
|
|||
import { FormProps } from '@/views/system/menu/utils/types';
|
||||
import { IconSelect } from '@/components/ReIcon';
|
||||
import Segmented from '@/components/ReSegmented';
|
||||
import ReAnimateSelector from '@/components/AnimateSelector';
|
||||
import { fixedTagOptions, frameLoadingOptions, hiddenTagOptions, keepAliveOptions, menuTypeOptions, showLinkOptions, showParentOptions } from '@/enums';
|
||||
import { $t } from '@/plugins/i18n';
|
||||
import { menuTypeOptions, showLinkOptions } from '@/enums';
|
||||
import { userMenuIconStore } from '@/store/modules/menuIcon';
|
||||
|
||||
const props = withDefaults(defineProps<FormProps>(), {
|
||||
formInline: () => ({
|
||||
menuType: 0,
|
||||
higherMenuOptions: [],
|
||||
parentId: 0,
|
||||
title: '',
|
||||
name: '',
|
||||
path: '',
|
||||
component: '',
|
||||
rank: 99,
|
||||
redirect: '',
|
||||
icon: '',
|
||||
enterTransition: '',
|
||||
leaveTransition: '',
|
||||
frameSrc: '',
|
||||
frameLoading: true,
|
||||
keepAlive: false,
|
||||
hiddenTag: false,
|
||||
fixedTag: false,
|
||||
showLink: true,
|
||||
showParent: false,
|
||||
}),
|
||||
formInline: () => ({}),
|
||||
});
|
||||
|
||||
const menuIconStore = userMenuIconStore();
|
||||
|
@ -66,7 +44,7 @@ defineExpose({ menuFormRef: ruleFormRef });
|
|||
placeholder="请选择上级菜单"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<span>{{ $t(data.title) }}</span>
|
||||
<span>{{ data.title }}</span>
|
||||
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
|
||||
</template>
|
||||
</el-cascader>
|
||||
|
@ -96,70 +74,26 @@ defineExpose({ menuFormRef: ruleFormRef });
|
|||
</re-col>
|
||||
|
||||
<re-col :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="菜单排序">
|
||||
<el-form-item v-model="newFormInline.rank" label="菜单排序" prop="rank">
|
||||
<el-input-number v-model="newFormInline.rank" :max="9999" :min="1" class="!w-full" controls-position="right" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col v-show="newFormInline.menuType === 0" :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="路由重定向">
|
||||
<el-input v-model="newFormInline.redirect" clearable placeholder="请输入默认跳转地址" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="菜单图标">
|
||||
<el-form-item label="菜单图标" prop="icon">
|
||||
<IconSelect v-model="newFormInline.icon" class="w-full" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col v-show="newFormInline.menuType < 2" :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="进场动画">
|
||||
<ReAnimateSelector v-model="newFormInline.enterTransition" placeholder="请选择页面进场加载动画" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col v-show="newFormInline.menuType < 2" :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="离场动画">
|
||||
<ReAnimateSelector v-model="newFormInline.leaveTransition" placeholder="请选择页面离场加载动画" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col v-show="newFormInline.menuType === 1" :sm="24" :value="12" :xs="24">
|
||||
<!-- iframe -->
|
||||
<el-form-item label="链接地址">
|
||||
<el-form-item label="链接地址" prop="frameSrc">
|
||||
<el-input v-model="newFormInline.frameSrc" clearable placeholder="请输入 iframe 链接地址" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col v-if="newFormInline.menuType === 1" :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="加载动画">
|
||||
<Segmented :modelValue="newFormInline.frameLoading ? 0 : 1" :options="frameLoadingOptions" @change="({ option: { value } }) => (newFormInline.frameLoading = value)" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="菜单">
|
||||
<Segmented :modelValue="newFormInline.showLink ? 0 : 1" :options="showLinkOptions" @change="({ option: { value } }) => (newFormInline.showLink = value)" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="父级菜单">
|
||||
<Segmented :modelValue="newFormInline.showParent ? 0 : 1" :options="showParentOptions" @change="({ option: { value } }) => (newFormInline.showParent = value)" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col v-show="newFormInline.menuType < 2" :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="缓存页面">
|
||||
<Segmented :modelValue="newFormInline.keepAlive ? 0 : 1" :options="keepAliveOptions" @change="({ option: { value } }) => (newFormInline.keepAlive = value)" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
|
||||
<re-col v-show="newFormInline.menuType < 2" :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="标签页">
|
||||
<Segmented :modelValue="newFormInline.hiddenTag ? 1 : 0" :options="hiddenTagOptions" @change="({ option: { value } }) => (newFormInline.hiddenTag = value)" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
<re-col v-show="newFormInline.menuType < 2" :sm="24" :value="12" :xs="24">
|
||||
<el-form-item label="固定标签页">
|
||||
<Segmented :modelValue="newFormInline.fixedTag ? 0 : 1" :options="fixedTagOptions" @change="({ option: { value } }) => (newFormInline.fixedTag = value)" />
|
||||
<el-form-item label="是否显示">
|
||||
<Segmented :modelValue="newFormInline.visible ? 0 : 1" :options="showLinkOptions" @change="({ option: { value } }) => (newFormInline.visible = value)" />
|
||||
</el-form-item>
|
||||
</re-col>
|
||||
</el-row>
|
||||
|
|
|
@ -7,10 +7,11 @@ 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 { dataList, handleDelete, loading, onSearch, openDialog, resetForm } from '@/views/system/menu/utils/hook';
|
||||
import { handleDelete, onAdd, onSearch, onUpdate, resetForm } from '@/views/system/menu/utils/hook';
|
||||
import form from '@/views/role/form.vue';
|
||||
import PureTable from '@pureadmin/table';
|
||||
import { columns } from '@/views/system/menu/utils/rule';
|
||||
import { userRouterStore } from '@/store/modules/router';
|
||||
|
||||
defineOptions({
|
||||
name: 'SystemMenu',
|
||||
|
@ -18,6 +19,7 @@ defineOptions({
|
|||
|
||||
const formRef = ref();
|
||||
const tableRef = ref();
|
||||
const routerStore = userRouterStore();
|
||||
|
||||
onMounted(() => {
|
||||
onSearch();
|
||||
|
@ -31,23 +33,23 @@ onMounted(() => {
|
|||
<el-input v-model="form.title" class="!w-[180px]" clearable placeholder="输入菜单名称" />
|
||||
</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('ri:search-line')" :loading="routerStore.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" :isExpandAll="false" :tableRef="tableRef?.getTableRef()" title="菜单管理" @fullscreen="tableRef.setAdaptive()" @refresh="onSearch">
|
||||
<template #buttons>
|
||||
<el-button :icon="useRenderIcon(AddFill)" type="primary" @click="openDialog()"> 新增菜单</el-button>
|
||||
<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="dataList"
|
||||
:data="routerStore.datalist"
|
||||
:header-cell-style="{ background: 'var(--el-fill-color-light)', color: 'var(--el-text-color-primary)' }"
|
||||
:loading="loading"
|
||||
:loading="routerStore.loading"
|
||||
:size="size"
|
||||
adaptive
|
||||
align-whole="center"
|
||||
|
@ -56,10 +58,8 @@ onMounted(() => {
|
|||
table-layout="auto"
|
||||
>
|
||||
<template #operation="{ row }">
|
||||
<el-button :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="openDialog('修改', row)"> 修改 </el-button>
|
||||
<el-button v-show="row.menuType !== 3" :icon="useRenderIcon(AddFill)" :size="size" class="reset-margin" link type="primary" @click="openDialog('新增', { parentId: row.id } as any)">
|
||||
新增
|
||||
</el-button>
|
||||
<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>
|
||||
<el-popconfirm :title="`是否确认删除菜单名称为${$t(row.title)}的这条数据${row?.children?.length > 0 ? '注意下级菜单也会一并删除,请谨慎操作' : ''}`" @confirm="handleDelete(row)">
|
||||
<template #reference>
|
||||
<el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary"> 删除 </el-button>
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import editForm from '../form.vue';
|
||||
import { handleTree } from '@/utils/tree';
|
||||
import { message } from '@/utils/message';
|
||||
import { getMenuList } from '@/api/v1/system';
|
||||
import { $t } from '@/plugins/i18n';
|
||||
import { addDialog } from '@/components/BaseDialog/index';
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import type { FormItemProps } from './types';
|
||||
|
||||
import { cloneDeep, deviceDetection, isAllEmpty } from '@pureadmin/utils';
|
||||
import { cloneDeep, deviceDetection } from '@pureadmin/utils';
|
||||
import { userRouterStore } from '@/store/modules/router';
|
||||
|
||||
const routerStore = userRouterStore();
|
||||
|
@ -15,8 +12,6 @@ export const form = reactive({
|
|||
title: '',
|
||||
});
|
||||
export const formRef = ref();
|
||||
export const dataList = ref([]);
|
||||
export const loading = ref(true);
|
||||
|
||||
/**
|
||||
* 标签栏菜单类型匹配
|
||||
|
@ -38,28 +33,19 @@ export const getMenuType = (type, text = false) => {
|
|||
* 表单重置
|
||||
* @param formEl
|
||||
*/
|
||||
export const resetForm = async formEl => {
|
||||
export const resetForm = async (formEl: any) => {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
await onSearch();
|
||||
};
|
||||
|
||||
/**
|
||||
* * 获取菜单数据
|
||||
*/
|
||||
export const onSearch = async () => {
|
||||
loading.value = true;
|
||||
|
||||
// 获取菜单数据
|
||||
const result: any = await getMenuList();
|
||||
if (result.code !== 200) message(result.message, { type: 'error' });
|
||||
|
||||
// 前端搜索菜单名称
|
||||
if (!isAllEmpty(form.title)) {
|
||||
result.data = result.data.filter(item => $t(item.title).includes(form.title));
|
||||
}
|
||||
|
||||
// 处理成树结构
|
||||
dataList.value = handleTree(result.data);
|
||||
|
||||
loading.value = false;
|
||||
routerStore.loading = true;
|
||||
await routerStore.getMenuList();
|
||||
routerStore.loading = false;
|
||||
};
|
||||
|
||||
export const formatHigherMenuOptions = (treeList: any) => {
|
||||
|
@ -73,30 +59,25 @@ export const formatHigherMenuOptions = (treeList: any) => {
|
|||
return newTreeList;
|
||||
};
|
||||
|
||||
export function openDialog(title = '新增', row?: FormItemProps) {
|
||||
/**
|
||||
* * 添加菜单
|
||||
*/
|
||||
export function onAdd(parentId: any = 0) {
|
||||
addDialog({
|
||||
title: `${title}菜单`,
|
||||
title: `新增菜单`,
|
||||
props: {
|
||||
formInline: {
|
||||
menuType: row?.menuType ?? 0,
|
||||
higherMenuOptions: formatHigherMenuOptions(cloneDeep(dataList.value)),
|
||||
parentId: row?.parentId ?? 0,
|
||||
title: row?.title ?? '',
|
||||
name: row?.name ?? '',
|
||||
path: row?.path ?? '',
|
||||
component: row?.component ?? '',
|
||||
rank: row?.rank ?? 99,
|
||||
redirect: row?.redirect ?? '',
|
||||
icon: row?.icon ?? '',
|
||||
enterTransition: row?.enterTransition ?? '',
|
||||
leaveTransition: row?.leaveTransition ?? '',
|
||||
frameSrc: row?.frameSrc ?? '',
|
||||
frameLoading: row?.frameLoading ?? true,
|
||||
keepAlive: row?.keepAlive ?? false,
|
||||
hiddenTag: row?.hiddenTag ?? false,
|
||||
fixedTag: row?.fixedTag ?? false,
|
||||
showLink: row?.showLink ?? true,
|
||||
showParent: row?.showParent ?? false,
|
||||
menuType: 0,
|
||||
higherMenuOptions: formatHigherMenuOptions(cloneDeep(routerStore.datalist)),
|
||||
parentId,
|
||||
title: '',
|
||||
name: '',
|
||||
path: '',
|
||||
component: '',
|
||||
rank: 99,
|
||||
icon: '',
|
||||
frameSrc: '',
|
||||
visible: true,
|
||||
},
|
||||
},
|
||||
width: '45%',
|
||||
|
@ -104,7 +85,7 @@ export function openDialog(title = '新增', row?: FormItemProps) {
|
|||
fullscreen: deviceDetection(),
|
||||
fullscreenIcon: true,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () => h(editForm, { ref: formRef, formInline: null }),
|
||||
contentRenderer: () => h(editForm, { ref: formRef }),
|
||||
beforeSure: (done, { options }) => {
|
||||
const menuFormRef = formRef.value.menuFormRef;
|
||||
const curData = options.props.formInline as FormItemProps;
|
||||
|
@ -112,24 +93,67 @@ export function openDialog(title = '新增', row?: FormItemProps) {
|
|||
if (!valid) return;
|
||||
delete curData.higherMenuOptions;
|
||||
|
||||
let result: boolean;
|
||||
if (title === '新增') {
|
||||
result = await routerStore.addMenu(curData);
|
||||
} else {
|
||||
curData.id = row.id;
|
||||
result = await routerStore.updateMenu(curData);
|
||||
}
|
||||
console.log(curData);
|
||||
|
||||
// 刷新表格数据
|
||||
if (result) {
|
||||
done();
|
||||
await onSearch();
|
||||
}
|
||||
// const result = await routerStore.addMenu(curData);
|
||||
// // 刷新表格数据
|
||||
// if (result) {
|
||||
// done();
|
||||
// await onSearch();
|
||||
// }
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* * 更新菜单
|
||||
* @param row
|
||||
*/
|
||||
export const onUpdate = (row?: FormItemProps) => {
|
||||
addDialog({
|
||||
title: `更新菜单`,
|
||||
props: {
|
||||
formInline: {
|
||||
menuType: row?.menuType,
|
||||
higherMenuOptions: formatHigherMenuOptions(cloneDeep(routerStore.datalist)),
|
||||
parentId: row?.parentId,
|
||||
title: row?.title,
|
||||
name: row?.name,
|
||||
path: row?.path,
|
||||
component: row?.component,
|
||||
rank: row?.rank,
|
||||
icon: row?.icon,
|
||||
frameSrc: row?.frameSrc,
|
||||
visible: row?.visible,
|
||||
},
|
||||
},
|
||||
width: '45%',
|
||||
draggable: true,
|
||||
fullscreen: deviceDetection(),
|
||||
fullscreenIcon: true,
|
||||
closeOnClickModal: false,
|
||||
contentRenderer: () => h(editForm, { ref: formRef }),
|
||||
beforeSure: (done, { options }) => {
|
||||
const menuFormRef = formRef.value.menuFormRef;
|
||||
const curData = options.props.formInline as FormItemProps;
|
||||
menuFormRef.validate(async (valid: any) => {
|
||||
if (!valid) return;
|
||||
delete curData.higherMenuOptions;
|
||||
|
||||
curData.id = row.id;
|
||||
console.log(row);
|
||||
// const result = await routerStore.updateMenu(curData);
|
||||
// // 刷新表格数据
|
||||
// if (result) {
|
||||
// done();
|
||||
// await onSearch();
|
||||
// }
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* * 删除菜单
|
||||
* @param row
|
||||
|
|
|
@ -9,17 +9,9 @@ interface FormItemProps {
|
|||
path: string;
|
||||
component: string;
|
||||
rank: number;
|
||||
redirect: string;
|
||||
icon: string;
|
||||
enterTransition: string;
|
||||
leaveTransition: string;
|
||||
frameSrc: string;
|
||||
frameLoading: boolean;
|
||||
keepAlive: boolean;
|
||||
hiddenTag: boolean;
|
||||
fixedTag: boolean;
|
||||
showLink: boolean;
|
||||
showParent: boolean;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
interface FormProps {
|
||||
|
|
|
@ -1,176 +1,119 @@
|
|||
<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";
|
||||
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: ""
|
||||
})
|
||||
formInline: () => ({
|
||||
title: '新增',
|
||||
higherDeptOptions: [],
|
||||
parentId: 0,
|
||||
nickname: '',
|
||||
username: '',
|
||||
password: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
sex: '',
|
||||
status: 1,
|
||||
remark: '',
|
||||
}),
|
||||
});
|
||||
|
||||
const sexOptions = [
|
||||
{
|
||||
value: 0,
|
||||
label: "男"
|
||||
},
|
||||
{
|
||||
value: 1,
|
||||
label: "女"
|
||||
}
|
||||
{
|
||||
value: 0,
|
||||
label: '男',
|
||||
},
|
||||
{
|
||||
value: 1,
|
||||
label: '女',
|
||||
},
|
||||
];
|
||||
const ruleFormRef = ref();
|
||||
const { switchStyle } = usePublicHooks();
|
||||
const newFormInline = ref(props.formInline);
|
||||
|
||||
function getRef() {
|
||||
return ruleFormRef.value;
|
||||
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>
|
||||
<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 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="邮箱" 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 :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>
|
||||
<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,53 +1,42 @@
|
|||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import ReCol from "@/components/MyCol";
|
||||
import { RoleFormProps } from "../utils/types";
|
||||
import { ref } from 'vue';
|
||||
import ReCol from '@/components/MyCol';
|
||||
import { RoleFormProps } from '../utils/types';
|
||||
|
||||
const props = withDefaults(defineProps<RoleFormProps>(), {
|
||||
formInline: () => ({
|
||||
username: "",
|
||||
nickname: "",
|
||||
roleOptions: [],
|
||||
ids: []
|
||||
})
|
||||
formInline: () => ({
|
||||
username: '',
|
||||
nickname: '',
|
||||
roleOptions: [],
|
||||
ids: [],
|
||||
}),
|
||||
});
|
||||
|
||||
const newFormInline = ref(props.formInline);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form :model="newFormInline">
|
||||
<el-row :gutter="30">
|
||||
<!-- <re-col>
|
||||
<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>
|
||||
<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,21 +1,21 @@
|
|||
<script setup lang="ts">
|
||||
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";
|
||||
<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";
|
||||
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"
|
||||
name: 'SystemUser',
|
||||
});
|
||||
|
||||
const treeRef = ref();
|
||||
|
@ -23,253 +23,139 @@ 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
|
||||
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"
|
||||
placeholder="请输入用户名称"
|
||||
clearable
|
||||
class="!w-[180px]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号码:" prop="phone">
|
||||
<el-input
|
||||
v-model="form.phone"
|
||||
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 :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
|
||||
title="用户管理(仅演示,操作后不生效)"
|
||||
:columns="columns"
|
||||
@refresh="onSearch"
|
||||
>
|
||||
<template #buttons>
|
||||
<el-button
|
||||
type="primary"
|
||||
:icon="useRenderIcon(AddFill)"
|
||||
@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
|
||||
style="font-size: var(--el-font-size-base)"
|
||||
class="text-[rgba(42,46,54,0.5)] dark:text-[rgba(220,220,242,0.5)]"
|
||||
>
|
||||
已选 {{ selectedNum }} 项
|
||||
</span>
|
||||
<el-button type="primary" text @click="onSelectionCancel">
|
||||
取消选择
|
||||
</el-button>
|
||||
</div>
|
||||
<el-popconfirm title="是否确认删除?" @confirm="onbatchDel">
|
||||
<template #reference>
|
||||
<el-button type="danger" text class="mr-1">
|
||||
批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</div>
|
||||
<pure-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
adaptive
|
||||
:adaptiveConfig="{ offsetBottom: 108 }"
|
||||
align-whole="center"
|
||||
table-layout="auto"
|
||||
:loading="loading"
|
||||
:size="size"
|
||||
: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.id}的这条数据`"
|
||||
@confirm="handleDelete(row)"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button
|
||||
class="reset-margin"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(Delete)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
<el-dropdown>
|
||||
<el-button
|
||||
class="ml-3 mt-[2px]"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(More)"
|
||||
@click="handleUpdate(row)"
|
||||
/>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>
|
||||
<el-button
|
||||
:class="buttonClass"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(Upload)"
|
||||
@click="handleUpload(row)"
|
||||
>
|
||||
上传头像
|
||||
</el-button>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<el-button
|
||||
:class="buttonClass"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(Password)"
|
||||
@click="handleReset(row)"
|
||||
>
|
||||
重置密码
|
||||
</el-button>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<el-button
|
||||
:class="buttonClass"
|
||||
link
|
||||
type="primary"
|
||||
:size="size"
|
||||
:icon="useRenderIcon(Role)"
|
||||
@click="handleRole(row)"
|
||||
>
|
||||
分配角色
|
||||
</el-button>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</pure-table>
|
||||
</template>
|
||||
</PureTableBar>
|
||||
</div>
|
||||
</div>
|
||||
<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 scoped lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-dropdown-menu__item i) {
|
||||
margin: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:deep(.el-button:focus-visible) {
|
||||
outline: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin: 24px 24px 0 !important;
|
||||
margin: 24px 24px 0 !important;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,141 +1,106 @@
|
|||
<script setup lang="ts">
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import { ref, computed, watch, getCurrentInstance } from "vue";
|
||||
<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 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";
|
||||
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[];
|
||||
id: number;
|
||||
name: string;
|
||||
highlight?: boolean;
|
||||
children?: Tree[];
|
||||
}
|
||||
|
||||
defineProps({
|
||||
treeLoading: Boolean,
|
||||
treeData: Array
|
||||
treeLoading: Boolean,
|
||||
treeData: Array,
|
||||
});
|
||||
|
||||
const emit = defineEmits(["tree-select"]);
|
||||
const emit = defineEmits(['tree-select']);
|
||||
|
||||
const treeRef = ref();
|
||||
const isExpand = ref(true);
|
||||
const searchValue = ref("");
|
||||
const searchValue = ref('');
|
||||
const highlightMap = ref({});
|
||||
const { proxy } = getCurrentInstance();
|
||||
const defaultProps = {
|
||||
children: "children",
|
||||
label: "name"
|
||||
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"
|
||||
];
|
||||
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);
|
||||
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 })
|
||||
);
|
||||
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;
|
||||
}
|
||||
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);
|
||||
highlightMap.value = {};
|
||||
searchValue.value = '';
|
||||
toggleRowExpansionAll(true);
|
||||
}
|
||||
|
||||
watch(searchValue, val => {
|
||||
treeRef.value!.filter(val);
|
||||
treeRef.value!.filter(val);
|
||||
});
|
||||
|
||||
defineExpose({ onTreeReset });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-loading="treeLoading"
|
||||
class="h-full bg-bg_color overflow-hidden relative"
|
||||
:style="{ minHeight: `calc(100vh - 141px)` }"
|
||||
>
|
||||
<div class="flex items-center h-[34px]">
|
||||
<el-input
|
||||
v-model="searchValue"
|
||||
class="ml-2"
|
||||
size="small"
|
||||
placeholder="请输入部门名称"
|
||||
clearable
|
||||
>
|
||||
<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
|
||||
class="w-[28px] cursor-pointer"
|
||||
width="18px"
|
||||
:icon="More2Fill"
|
||||
/>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>
|
||||
<el-button
|
||||
:class="buttonClass"
|
||||
link
|
||||
type="primary"
|
||||
:icon="useRenderIcon(isExpand ? ExpandIcon : UnExpandIcon)"
|
||||
@click="toggleRowExpansionAll(isExpand ? false : true)"
|
||||
>
|
||||
{{ isExpand ? "折叠全部" : "展开全部" }}
|
||||
</el-button>
|
||||
</el-dropdown-item>
|
||||
<!-- <el-dropdown-item>
|
||||
<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
|
||||
|
@ -146,70 +111,46 @@ defineExpose({ 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"
|
||||
node-key="id"
|
||||
size="small"
|
||||
:props="defaultProps"
|
||||
default-expand-all
|
||||
:expand-on-click-node="false"
|
||||
:filter-node-method="filterNode"
|
||||
@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 class="!w-[120px] !truncate" :title="node.label">
|
||||
{{ node.label }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</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;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:deep(.el-tree) {
|
||||
--el-tree-node-hover-bg-color: transparent;
|
||||
--el-tree-node-hover-bg-color: transparent;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,538 +1,456 @@
|
|||
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";
|
||||
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([]);
|
||||
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 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 handleUpdate(row) {
|
||||
console.log(row);
|
||||
}
|
||||
|
||||
function handleDelete(row) {
|
||||
message(`您删除了用户编号为${row.id}的这条数据`, { type: "success" });
|
||||
onSearch();
|
||||
}
|
||||
function handleDelete(row) {
|
||||
message(`您删除了用户编号为${row.id}的这条数据`, { type: 'success' });
|
||||
onSearch();
|
||||
}
|
||||
|
||||
function handleSizeChange(val: number) {
|
||||
console.log(`${val} items per page`);
|
||||
}
|
||||
function handleSizeChange(val: number) {
|
||||
console.log(`${val} items per page`);
|
||||
}
|
||||
|
||||
function handleCurrentChange(val: number) {
|
||||
console.log(`current page: ${val}`);
|
||||
}
|
||||
function handleCurrentChange(val: number) {
|
||||
console.log(`current page: ${val}`);
|
||||
}
|
||||
|
||||
/** 当CheckBox选择项发生变化时会触发该事件 */
|
||||
function handleSelectionChange(val) {
|
||||
selectedNum.value = val.length;
|
||||
// 重置表格高度
|
||||
tableRef.value.setAdaptive();
|
||||
}
|
||||
/** 当CheckBox选择项发生变化时会触发该事件 */
|
||||
function handleSelectionChange(val) {
|
||||
selectedNum.value = val.length;
|
||||
// 重置表格高度
|
||||
tableRef.value.setAdaptive();
|
||||
}
|
||||
|
||||
/** 取消选择 */
|
||||
function onSelectionCancel() {
|
||||
selectedNum.value = 0;
|
||||
// 用于多选表格,清空用户的选择
|
||||
tableRef.value.getTableRef().clearSelection();
|
||||
}
|
||||
/** 取消选择 */
|
||||
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();
|
||||
}
|
||||
/** 批量删除 */
|
||||
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;
|
||||
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);
|
||||
}
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
const resetForm = formEl => {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
form.deptId = "";
|
||||
treeRef.value.onTreeReset();
|
||||
onSearch();
|
||||
};
|
||||
const resetForm = formEl => {
|
||||
if (!formEl) return;
|
||||
formEl.resetFields();
|
||||
form.deptId = '';
|
||||
treeRef.value.onTreeReset();
|
||||
onSearch();
|
||||
};
|
||||
|
||||
function onTreeSelect({ id, selected }) {
|
||||
form.deptId = selected ? id : "";
|
||||
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 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 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(); // 刷新表格数据
|
||||
}
|
||||
function chores() {
|
||||
message(`您${title}了用户名称为${curData.username}的这条数据`, {
|
||||
type: 'success',
|
||||
});
|
||||
done(); // 关闭弹框
|
||||
onSearch(); // 刷新表格数据
|
||||
}
|
||||
|
||||
FormRef.validate(valid => {
|
||||
if (valid) {
|
||||
console.log("curData", curData);
|
||||
// 表单规则校验通过
|
||||
if (title === "新增") {
|
||||
// 实际开发先调用新增接口,再进行下面操作
|
||||
chores();
|
||||
} else {
|
||||
// 实际开发先调用修改接口,再进行下面操作
|
||||
chores();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
FormRef.validate(valid => {
|
||||
if (valid) {
|
||||
console.log('curData', curData);
|
||||
// 表单规则校验通过
|
||||
if (title === '新增') {
|
||||
// 实际开发先调用新增接口,再进行下面操作
|
||||
chores();
|
||||
} else {
|
||||
// 实际开发先调用修改接口,再进行下面操作
|
||||
chores();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const cropRef = ref();
|
||||
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()
|
||||
});
|
||||
}
|
||||
/** 上传头像 */
|
||||
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)
|
||||
);
|
||||
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(); // 刷新表格数据
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
/** 重置密码 */
|
||||
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(); // 关闭弹框
|
||||
}
|
||||
});
|
||||
}
|
||||
/** 分配角色 */
|
||||
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();
|
||||
onMounted(async () => {
|
||||
treeLoading.value = true;
|
||||
onSearch();
|
||||
|
||||
// 归属部门
|
||||
const { data } = await getDeptList();
|
||||
higherDeptOptions.value = handleTree(data);
|
||||
treeData.value = handleTree(data);
|
||||
treeLoading.value = false;
|
||||
// 归属部门
|
||||
const { data } = await getDeptList();
|
||||
higherDeptOptions.value = handleTree(data);
|
||||
treeData.value = handleTree(data);
|
||||
treeLoading.value = false;
|
||||
|
||||
// 角色列表
|
||||
roleOptions.value = (await getAllRoleList()).data;
|
||||
});
|
||||
// 角色列表
|
||||
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
|
||||
};
|
||||
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,39 +1,39 @@
|
|||
import { reactive } from "vue";
|
||||
import type { FormRules } from "element-plus";
|
||||
import { isPhone, isEmail } from "@pureadmin/utils";
|
||||
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"
|
||||
}
|
||||
]
|
||||
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,36 +1,38 @@
|
|||
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;
|
||||
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;
|
||||
formInline: FormItemProps;
|
||||
}
|
||||
|
||||
interface RoleFormItemProps {
|
||||
username: string;
|
||||
nickname: string;
|
||||
/** 角色列表 */
|
||||
roleOptions: any[];
|
||||
/** 选中的角色列表 */
|
||||
ids: Record<number, unknown>[];
|
||||
username: string;
|
||||
nickname: string;
|
||||
/** 角色列表 */
|
||||
roleOptions: any[];
|
||||
/** 选中的角色列表 */
|
||||
ids: Record<number, unknown>[];
|
||||
}
|
||||
|
||||
interface RoleFormProps {
|
||||
formInline: RoleFormItemProps;
|
||||
formInline: RoleFormItemProps;
|
||||
}
|
||||
|
||||
export type { FormItemProps, FormProps, RoleFormItemProps, RoleFormProps };
|
||||
|
|