feat: 整合路由页面

This commit is contained in:
valarchie 2023-07-07 22:19:42 +08:00
parent caba16e30f
commit b0c2c47607
13 changed files with 2981 additions and 37 deletions

155
pnpm-lock.yaml generated
View File

@ -28,6 +28,7 @@ specifiers:
autoprefixer: ^10.4.14
axios: ^1.4.0
cloc: ^2.11.0
crypto-js: ^4.1.1
cssnano: ^6.0.1
dayjs: ^1.11.8
echarts: ^5.4.2
@ -37,6 +38,7 @@ specifiers:
eslint-plugin-vue: ^9.15.1
husky: ^8.0.3
js-cookie: ^3.0.5
jsencrypt: ^3.3.2
lint-staged: ^13.2.2
mitt: ^3.0.0
mockjs: ^1.1.0
@ -51,6 +53,7 @@ specifiers:
postcss-scss: ^4.0.6
prettier: ^2.8.8
pretty-quick: ^3.1.3
qrcode: ^1.5.3
qs: ^6.11.2
responsive-storage: ^2.2.0
rimraf: ^5.0.1
@ -72,6 +75,7 @@ specifiers:
svgo: ^3.0.2
tailwindcss: ^3.3.2
terser: ^5.18.1
typeit: ^8.7.1
typescript: 5.0.4
vite: ^4.3.9
vite-plugin-cdn-import: ^0.3.5
@ -93,19 +97,23 @@ dependencies:
"@vueuse/motion": 2.0.0_vue@3.3.4
animate.css: 4.1.1
axios: 1.4.0
crypto-js: 4.1.1
dayjs: 1.11.8
echarts: 5.4.2
element-plus: 2.3.6_vue@3.3.4
js-cookie: 3.0.5
jsencrypt: 3.3.2
mitt: 3.0.0
mockjs: 1.1.0
nprogress: 0.2.0
path: 0.12.7
pinia: 2.1.4_typescript@5.0.4+vue@3.3.4
pinyin-pro: 3.15.2
qrcode: 1.5.3
qs: 6.11.2
responsive-storage: 2.2.0
sortablejs: 1.15.0
typeit: 8.7.1
vue: 3.3.4
vue-router: 4.2.2_vue@3.3.4
vue-types: 5.1.0_vue@3.3.4
@ -2373,7 +2381,6 @@ packages:
integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
}
engines: { node: ">=8" }
dev: true
/ansi-regex/6.0.1:
resolution:
@ -2400,7 +2407,6 @@ packages:
engines: { node: ">=8" }
dependencies:
color-convert: 2.0.1
dev: true
/ansi-styles/6.2.1:
resolution:
@ -2699,7 +2705,6 @@ packages:
integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
}
engines: { node: ">=6" }
dev: true
/camelcase/6.3.0:
resolution:
@ -2834,6 +2839,17 @@ packages:
string-width: 5.1.2
dev: true
/cliui/6.0.0:
resolution:
{
integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
}
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
wrap-ansi: 6.2.0
dev: false
/cliui/8.0.1:
resolution:
{
@ -2870,7 +2886,6 @@ packages:
engines: { node: ">=7.0.0" }
dependencies:
color-name: 1.1.4
dev: true
/color-name/1.1.3:
resolution:
@ -2883,7 +2898,6 @@ packages:
{
integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
}
dev: true
/color-string/1.9.1:
resolution:
@ -3110,6 +3124,13 @@ packages:
which: 2.0.2
dev: true
/crypto-js/4.1.1:
resolution:
{
integrity: sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==
}
dev: false
/css-declaration-sorter/6.4.0_postcss@8.4.24:
resolution:
{
@ -3450,7 +3471,6 @@ packages:
integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
}
engines: { node: ">=0.10.0" }
dev: true
/deep-is/0.1.4:
resolution:
@ -3514,6 +3534,13 @@ packages:
engines: { node: ">=0.3.1" }
dev: true
/dijkstrajs/1.0.3:
resolution:
{
integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==
}
dev: false
/dir-glob/3.0.1:
resolution:
{
@ -3693,7 +3720,6 @@ packages:
{
integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
}
dev: true
/emoji-regex/9.2.2:
resolution:
@ -3702,6 +3728,13 @@ packages:
}
dev: true
/encode-utf8/1.0.3:
resolution:
{
integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==
}
dev: false
/encodeurl/1.0.2:
resolution:
{
@ -4235,7 +4268,6 @@ packages:
dependencies:
locate-path: 5.0.0
path-exists: 4.0.0
dev: true
/find-up/5.0.0:
resolution:
@ -4398,7 +4430,6 @@ packages:
integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
}
engines: { node: 6.* || 8.* || >= 10.* }
dev: true
/get-intrinsic/1.2.1:
resolution:
@ -4938,7 +4969,6 @@ packages:
integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
}
engines: { node: ">=8" }
dev: true
/is-fullwidth-code-point/4.0.0:
resolution:
@ -5112,6 +5142,13 @@ packages:
argparse: 2.0.1
dev: true
/jsencrypt/3.3.2:
resolution:
{
integrity: sha512-arQR1R1ESGdAxY7ZheWr12wCaF2yF47v5qpB76TtV64H1pyGudk9Hvw8Y9tb/FiTIaaTRUyaSnm5T/Y53Ghm/A==
}
dev: false
/jsesc/2.5.2:
resolution:
{
@ -5297,7 +5334,6 @@ packages:
engines: { node: ">=8" }
dependencies:
p-locate: 4.1.0
dev: true
/locate-path/6.0.0:
resolution:
@ -6061,7 +6097,6 @@ packages:
engines: { node: ">=6" }
dependencies:
p-try: 2.2.0
dev: true
/p-limit/3.1.0:
resolution:
@ -6081,7 +6116,6 @@ packages:
engines: { node: ">=8" }
dependencies:
p-limit: 2.3.0
dev: true
/p-locate/5.0.0:
resolution:
@ -6109,7 +6143,6 @@ packages:
integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
}
engines: { node: ">=6" }
dev: true
/parent-module/1.0.1:
resolution:
@ -6148,7 +6181,6 @@ packages:
integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
}
engines: { node: ">=8" }
dev: true
/path-is-absolute/1.0.1:
resolution:
@ -6310,6 +6342,14 @@ packages:
dev: false
optional: true
/pngjs/5.0.0:
resolution:
{
integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
}
engines: { node: ">=10.13.0" }
dev: false
/popmotion/11.0.5:
resolution:
{
@ -7313,6 +7353,20 @@ packages:
engines: { node: ">=0.6.0", teleport: ">=0.2.0" }
dev: true
/qrcode/1.5.3:
resolution:
{
integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==
}
engines: { node: ">=10.13.0" }
hasBin: true
dependencies:
dijkstrajs: 1.0.3
encode-utf8: 1.0.3
pngjs: 5.0.0
yargs: 15.4.1
dev: false
/qs/6.11.2:
resolution:
{
@ -7437,7 +7491,6 @@ packages:
integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
}
engines: { node: ">=0.10.0" }
dev: true
/require-from-string/2.0.2:
resolution:
@ -7447,6 +7500,13 @@ packages:
engines: { node: ">=0.10.0" }
dev: true
/require-main-filename/2.0.0:
resolution:
{
integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
}
dev: false
/resolve-from/4.0.0:
resolution:
{
@ -7702,6 +7762,13 @@ packages:
dependencies:
lru-cache: 6.0.0
/set-blocking/2.0.0:
resolution:
{
integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
}
dev: false
/shebang-command/2.0.0:
resolution:
{
@ -7947,7 +8014,6 @@ packages:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
dev: true
/string-width/5.1.2:
resolution:
@ -7988,7 +8054,6 @@ packages:
engines: { node: ">=8" }
dependencies:
ansi-regex: 5.0.1
dev: true
/strip-ansi/7.1.0:
resolution:
@ -8711,6 +8776,14 @@ packages:
engines: { node: ">=8" }
dev: true
/typeit/8.7.1:
resolution:
{
integrity: sha512-Bx/O4NMz10NWh9FWYtVwV4XwGHF9UDJfpCZPJRtw2/oUcahFAStU8J0t19aroPfTV6s1UlS5ICoqilOqmEnh2Q==
}
requiresBuild: true
dev: false
/typescript/5.0.4:
resolution:
{
@ -9109,6 +9182,13 @@ packages:
dev: false
optional: true
/which-module/2.0.1:
resolution:
{
integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==
}
dev: false
/which/1.3.1:
resolution:
{
@ -9148,7 +9228,6 @@ packages:
ansi-styles: 4.3.0
string-width: 4.2.3
strip-ansi: 6.0.1
dev: true
/wrap-ansi/7.0.0:
resolution:
@ -9200,6 +9279,13 @@ packages:
engines: { node: ">=12" }
dev: true
/y18n/4.0.3:
resolution:
{
integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
}
dev: false
/y18n/5.0.8:
resolution:
{
@ -9236,6 +9322,17 @@ packages:
engines: { node: ">= 14" }
dev: true
/yargs-parser/18.1.3:
resolution:
{
integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
}
engines: { node: ">=6" }
dependencies:
camelcase: 5.3.1
decamelize: 1.2.0
dev: false
/yargs-parser/20.2.9:
resolution:
{
@ -9252,6 +9349,26 @@ packages:
engines: { node: ">=12" }
dev: true
/yargs/15.4.1:
resolution:
{
integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
}
engines: { node: ">=8" }
dependencies:
cliui: 6.0.0
decamelize: 1.2.0
find-up: 4.1.0
get-caller-file: 2.0.5
require-directory: 2.1.1
require-main-filename: 2.0.0
set-blocking: 2.0.0
string-width: 4.2.3
which-module: 2.0.1
y18n: 4.0.3
yargs-parser: 18.1.3
dev: false
/yargs/17.7.2:
resolution:
{

35
src/api/system.ts Normal file
View File

@ -0,0 +1,35 @@
import { http } from "@/utils/http";
type Result = {
success: boolean;
data?: {
/** 列表数据 */
list: Array<any>;
/** 总条目数 */
total?: number;
/** 每页显示条目个数 */
pageSize?: number;
/** 当前页数 */
currentPage?: number;
};
};
type ResultDept = {
success: boolean;
data?: Array<any>;
};
/** 获取用户管理列表 */
export const getUserList = (data?: object) => {
return http.request<Result>("post", "/user", { data });
};
/** 获取角色管理列表 */
export const getRoleList = (data?: object) => {
return http.request<Result>("post", "/role", { data });
};
/** 获取部门管理列表 */
export const getDeptList = (data?: object) => {
return http.request<ResultDept>("post", "/dept", { data });
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,237 @@
<script setup lang="ts">
import { cloneDeep } from "@pureadmin/utils";
import { IconJson } from "@/components/ReIcon/data";
import { ref, computed, CSSProperties, toRef, watch } from "vue";
type ParameterCSSProperties = (item?: string) => CSSProperties | undefined;
defineOptions({
name: "IconSelect"
});
const props = defineProps({
modelValue: {
require: false,
type: String
}
});
const emit = defineEmits<{ (e: "update:modelValue", v: string) }>();
const visible = ref(false);
const inputValue = toRef(props, "modelValue");
const iconList = ref(IconJson);
const icon = ref("add-location");
const currentActiveType = ref("ep:");
//
const copyIconList = cloneDeep(iconList.value);
const pageSize = ref(96);
const currentPage = ref(1);
//
const filterValue = ref("");
const tabsList = [
{
label: "Element Plus",
name: "ep:"
},
{
label: "Font Awesome 4",
name: "fa:"
},
{
label: "Font Awesome 5 Solid",
name: "fa-solid:"
}
];
const pageList = computed(() => {
if (currentPage.value === 1) {
return copyIconList[currentActiveType.value]
.filter(v => v.includes(filterValue.value))
.slice(currentPage.value - 1, pageSize.value);
} else {
return copyIconList[currentActiveType.value]
.filter(v => v.includes(filterValue.value))
.slice(
pageSize.value * (currentPage.value - 1),
pageSize.value * (currentPage.value - 1) + 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 handleClick({ props }) {
currentPage.value = 1;
currentActiveType.value = props.name;
emit(
"update:modelValue",
currentActiveType.value + iconList.value[currentActiveType.value][0]
);
icon.value = iconList.value[currentActiveType.value][0];
}
function onChangeIcon(item) {
icon.value = item;
emit("update:modelValue", currentActiveType.value + item);
visible.value = false;
}
function onCurrentChange(page) {
currentPage.value = page;
}
watch(
() => {
return props.modelValue;
},
() => {
if (props.modelValue) {
currentActiveType.value = props.modelValue.substring(
0,
props.modelValue.indexOf(":") + 1
);
icon.value = props.modelValue.substring(
props.modelValue.indexOf(":") + 1
);
}
},
{ immediate: true }
);
watch(
() => {
return filterValue.value;
},
() => {
currentPage.value = 1;
}
);
</script>
<template>
<div class="selector w-[350px]">
<el-input v-model="inputValue" disabled>
<template #append>
<el-popover
:width="350"
trigger="click"
popper-class="pure-popper"
:popper-options="{
placement: 'auto'
}"
:visible="visible"
>
<template #reference>
<div
class="w-[40px] h-[32px] cursor-pointer flex justify-center items-center"
@click="visible = !visible"
>
<IconifyIconOnline :icon="currentActiveType + icon" />
</div>
</template>
<el-input
class="px-2 pt-2"
v-model="filterValue"
placeholder="搜索图标"
clearable
/>
<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"
:title="item"
class="icon-item p-2 cursor-pointer mr-2 mt-1 flex justify-center items-center border border-solid"
:style="iconItemStyle(item)"
@click="onChangeIcon(item)"
>
<IconifyIconOnline
:icon="currentActiveType + item"
width="20px"
height="20px"
/>
</li>
</ul>
</el-scrollbar>
</el-tab-pane>
</el-tabs>
<el-pagination
small
:total="copyIconList[currentActiveType].length"
:page-size="pageSize"
:current-page="currentPage"
background
layout="prev, pager, next"
class="flex items-center justify-center h-10"
@current-change="onCurrentChange"
/>
</el-popover>
</template>
</el-input>
</div>
</template>
<style lang="scss" scoped>
.icon-item {
&:hover {
color: var(--el-color-primary);
border-color: var(--el-color-primary);
transition: all 0.4s;
transform: scaleX(1.05);
}
}
:deep(.el-tabs__nav-next) {
font-size: 15px;
line-height: 32px;
box-shadow: -5px 0 5px -6px #ccc;
}
:deep(.el-tabs__nav-prev) {
font-size: 15px;
line-height: 32px;
box-shadow: 5px 0 5px -6px #ccc;
}
:deep(.el-input-group__append) {
padding: 0;
}
:deep(.el-tabs__item) {
height: 30px;
font-size: 12px;
font-weight: normal;
line-height: 30px;
}
:deep(.el-tabs__header),
:deep(.el-tabs__nav-wrap) {
position: static;
margin: 0;
box-shadow: 0 2px 5px rgb(0 0 0 / 6%);
}
:deep(.el-tabs__content) {
margin-top: 4px;
}
</style>

View File

@ -5,10 +5,41 @@ import { addIcon } from "@iconify/vue/dist/offline";
*/
// 本地菜单图标后端在路由的icon中返回对应的图标字符串并且前端在此处使用addIcon添加即可渲染菜单图标
import HomeFilled from "@iconify-icons/ep/home-filled";
import UbuntuFill from "@iconify-icons/ri/ubuntu-fill";
import Menu from "@iconify-icons/ep/menu";
import Edit from "@iconify-icons/ep/edit";
import InformationLine from "@iconify-icons/ri/information-line";
import SetUp from "@iconify-icons/ep/set-up";
import TerminalWindowLine from "@iconify-icons/ri/terminal-window-line";
import Guide from "@iconify-icons/ep/guide";
import HomeFilled from "@iconify-icons/ep/home-filled";
import Card from "@iconify-icons/ri/bank-card-line";
import ListCheck from "@iconify-icons/ri/list-check";
import Histogram from "@iconify-icons/ep/histogram";
import Ppt from "@iconify-icons/ri/file-ppt-2-line";
import CheckboxCircleLine from "@iconify-icons/ri/checkbox-circle-line";
import FlUser from "@iconify-icons/ri/admin-line";
import Role from "@iconify-icons/ri/admin-fill";
import Setting from "@iconify-icons/ri/settings-3-line";
import Dept from "@iconify-icons/ri/git-branch-line";
import Lollipop from "@iconify-icons/ep/lollipop";
addIcon("homeFilled", HomeFilled);
import Monitor from "@iconify-icons/ep/monitor";
addIcon("ubuntuFill", UbuntuFill);
addIcon("menu", Menu);
addIcon("edit", Edit);
addIcon("informationLine", InformationLine);
addIcon("setUp", SetUp);
addIcon("terminalWindowLine", TerminalWindowLine);
addIcon("guide", Guide);
addIcon("homeFilled", HomeFilled);
addIcon("card", Card);
addIcon("listCheck", ListCheck);
addIcon("histogram", Histogram);
addIcon("ppt", Ppt);
addIcon("checkboxCircleLine", CheckboxCircleLine);
addIcon("flUser", FlUser);
addIcon("role", Role);
addIcon("setting", Setting);
addIcon("dept", Dept);
addIcon("lollipop", Lollipop);
addIcon("monitor", Monitor);

View File

@ -1,12 +1,6 @@
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
import { delay, getKeyList, cloneDeep } from "@pureadmin/utils";
import { defineComponent, ref, computed, type PropType, nextTick } from "vue";
import {
delay,
cloneDeep,
isBoolean,
isFunction,
getKeyList
} from "@pureadmin/utils";
import Sortable from "sortablejs";
import DragIcon from "./svg/drag.svg?component";
@ -43,13 +37,8 @@ export default defineComponent({
const loading = ref(false);
const checkAll = ref(true);
const isIndeterminate = ref(false);
const filterColumns = cloneDeep(props?.columns).filter(column =>
isBoolean(column?.hide)
? !column.hide
: !(isFunction(column?.hide) && column?.hide())
);
let checkColumnList = getKeyList(cloneDeep(props?.columns), "label");
const checkedColumns = ref(getKeyList(cloneDeep(filterColumns), "label"));
const checkedColumns = ref(checkColumnList);
const dynamicColumns = ref(cloneDeep(props?.columns));
const getDropdownItemStyle = computed(() => {
@ -131,7 +120,7 @@ export default defineComponent({
dynamicColumns.value = cloneDeep(props?.columns);
checkColumnList = [];
checkColumnList = await getKeyList(cloneDeep(props?.columns), "label");
checkedColumns.value = getKeyList(cloneDeep(filterColumns), "label");
checkedColumns.value = checkColumnList;
}
const dropdown = {

View File

@ -4,7 +4,7 @@ export default {
meta: {
icon: "informationLine",
title: "异常页面",
// showLink: false,
showLink: false,
rank: 9
},
children: [

39
src/views/system/hooks.ts Normal file
View File

@ -0,0 +1,39 @@
// 抽离可公用的工具函数等用于系统管理页面逻辑
import { computed } from "vue";
import { useDark } from "@pureadmin/utils";
export function usePublicHooks() {
const { isDark } = useDark();
const switchStyle = computed(() => {
return {
"--el-switch-on-color": "#6abe39",
"--el-switch-off-color": "#e84749"
};
});
const tagStyle = computed(() => {
return (status: number) => {
return status === 1
? {
"--el-tag-text-color": isDark.value ? "#6abe39" : "#389e0d",
"--el-tag-bg-color": isDark.value ? "#172412" : "#f6ffed",
"--el-tag-border-color": isDark.value ? "#274a17" : "#b7eb8f"
}
: {
"--el-tag-text-color": isDark.value ? "#e84749" : "#cf1322",
"--el-tag-bg-color": isDark.value ? "#2b1316" : "#fff1f0",
"--el-tag-border-color": isDark.value ? "#58191c" : "#ffa39e"
};
};
});
return {
/** 当前网页是否为`dark`模式 */
isDark,
/** 表现更鲜明的`el-switch`组件 */
switchStyle,
/** 表现更鲜明的`el-tag`组件 */
tagStyle
};
}

View File

@ -0,0 +1,55 @@
<script setup lang="ts">
import { ref } from "vue";
import { formRules } from "./utils/rule";
import { FormProps } from "./utils/types";
const props = withDefaults(defineProps<FormProps>(), {
formInline: () => ({
name: "",
code: "",
remark: ""
})
});
const ruleFormRef = ref();
const newFormInline = ref(props.formInline);
function getRef() {
return ruleFormRef.value;
}
defineExpose({ getRef });
</script>
<template>
<el-form
ref="ruleFormRef"
:model="newFormInline"
:rules="formRules"
label-width="82px"
>
<el-form-item label="角色名称" prop="name">
<el-input
v-model="newFormInline.name"
clearable
placeholder="请输入角色名称"
/>
</el-form-item>
<el-form-item label="角色标识" prop="code">
<el-input
v-model="newFormInline.code"
clearable
placeholder="请输入角色标识"
/>
</el-form-item>
<el-form-item label="备注">
<el-input
v-model="newFormInline.remark"
placeholder="请输入备注信息"
type="textarea"
/>
</el-form-item>
</el-form>
</template>

View File

@ -0,0 +1,216 @@
<script setup lang="ts">
import { ref } from "vue";
import { useRole } from "./utils/hook";
import { PureTableBar } from "@/components/RePureTableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
// import Database from "@iconify-icons/ri/database-2-line";
// import More from "@iconify-icons/ep/more-filled";
import Delete from "@iconify-icons/ep/delete";
import EditPen from "@iconify-icons/ep/edit-pen";
import Search from "@iconify-icons/ep/search";
import Refresh from "@iconify-icons/ep/refresh";
import Menu from "@iconify-icons/ep/menu";
import AddFill from "@iconify-icons/ri/add-circle-line";
defineOptions({
name: "Role"
});
const formRef = ref();
const {
form,
loading,
columns,
dataList,
pagination,
// buttonClass,
onSearch,
resetForm,
openDialog,
handleMenu,
handleDelete,
// handleDatabase,
handleSizeChange,
handleCurrentChange,
handleSelectionChange
} = useRole();
</script>
<template>
<div class="main">
<el-form
ref="formRef"
:inline="true"
:model="form"
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]"
>
<el-form-item label="角色名称:" prop="name">
<el-input
v-model="form.name"
placeholder="请输入角色名称"
clearable
class="!w-[200px]"
/>
</el-form-item>
<el-form-item label="角色标识:" prop="code">
<el-input
v-model="form.code"
placeholder="请输入角色标识"
clearable
class="!w-[180px]"
/>
</el-form-item>
<el-form-item label="状态:" prop="status">
<el-select
v-model="form.status"
placeholder="请选择状态"
clearable
class="!w-[180px]"
>
<el-option label="已启用" value="1" />
<el-option label="已停用" value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button
type="primary"
:icon="useRenderIcon(Search)"
:loading="loading"
@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 }">
<pure-table
border
align-whole="center"
showOverflowTooltip
table-layout="auto"
:loading="loading"
:size="size"
adaptive
:data="dataList"
:columns="dynamicColumns"
:pagination="pagination"
:paginationSmall="size === 'small' ? true : false"
:header-cell-style="{
background: 'var(--el-table-row-hover-bg-color)',
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-button
class="reset-margin"
link
type="primary"
:size="size"
:icon="useRenderIcon(Menu)"
@click="handleMenu"
>
菜单权限
</el-button>
<el-popconfirm
:title="`是否确认删除角色名称为${row.name}的这条数据`"
@confirm="handleDelete(row)"
>
<template #reference>
<el-button
class="reset-margin"
link
type="primary"
:size="size"
:icon="useRenderIcon(Delete)"
>
删除
</el-button>
</template>
</el-popconfirm>
<!-- <el-dropdown>
<el-button
class="ml-3 mt-[2px]"
link
type="primary"
:size="size"
:icon="useRenderIcon(More)"
/>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>
<el-button
:class="buttonClass"
link
type="primary"
:size="size"
:icon="useRenderIcon(Menu)"
@click="handleMenu"
>
菜单权限
</el-button>
</el-dropdown-item>
<el-dropdown-item>
<el-button
:class="buttonClass"
link
type="primary"
:size="size"
:icon="useRenderIcon(Database)"
@click="handleDatabase"
>
数据权限
</el-button>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown> -->
</template>
</pure-table>
</template>
</PureTableBar>
</div>
</template>
<style scoped lang="scss">
:deep(.el-dropdown-menu__item i) {
margin: 0;
}
.search-form {
:deep(.el-form-item) {
margin-bottom: 12px;
}
}
</style>

View File

@ -0,0 +1,241 @@
import dayjs from "dayjs";
import editForm from "../form.vue";
import { message } from "@/utils/message";
import { getRoleList } from "@/api/system";
import { ElMessageBox } from "element-plus";
import { usePublicHooks } from "../../hooks";
import { addDialog } from "@/components/ReDialog";
import { type FormItemProps } from "../utils/types";
import { type PaginationProps } from "@pureadmin/table";
import { reactive, ref, onMounted, h, toRaw } from "vue";
export function useRole() {
const form = reactive({
name: "",
code: "",
status: ""
});
const formRef = ref();
const dataList = ref([]);
const loading = ref(true);
const switchLoadMap = ref({});
const { switchStyle } = usePublicHooks();
const pagination = reactive<PaginationProps>({
total: 0,
pageSize: 10,
currentPage: 1,
background: true
});
const columns: TableColumnList = [
{
label: "角色编号",
prop: "id",
minWidth: 100
},
{
label: "角色名称",
prop: "name",
minWidth: 120
},
{
label: "角色标识",
prop: "code",
minWidth: 150
},
{
label: "状态",
minWidth: 130,
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: "备注",
prop: "remark",
minWidth: 150
},
{
label: "创建时间",
minWidth: 180,
prop: "createTime",
formatter: ({ createTime }) =>
dayjs(createTime).format("YYYY-MM-DD HH:mm:ss")
},
{
label: "操作",
fixed: "right",
width: 240,
slot: "operation"
}
];
// const buttonClass = computed(() => {
// return [
// "!h-[20px]",
// "reset-margin",
// "!text-gray-500",
// "dark:!text-white",
// "dark:hover:!text-primary"
// ];
// });
function onChange({ row, index }) {
ElMessageBox.confirm(
`确认要<strong>${
row.status === 0 ? "停用" : "启用"
}</strong><strong style='color:var(--el-color-primary)'>${
row.name
}</strong>?`,
"系统提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
dangerouslyUseHTMLString: true,
draggable: true
}
)
.then(() => {
switchLoadMap.value[index] = Object.assign(
{},
switchLoadMap.value[index],
{
loading: true
}
);
setTimeout(() => {
switchLoadMap.value[index] = Object.assign(
{},
switchLoadMap.value[index],
{
loading: false
}
);
message(`${row.status === 0 ? "停用" : "启用"}${row.name}`, {
type: "success"
});
}, 300);
})
.catch(() => {
row.status === 0 ? (row.status = 1) : (row.status = 0);
});
}
function handleDelete(row) {
message(`您删除了角色名称为${row.name}的这条数据`, { type: "success" });
onSearch();
}
function handleSizeChange(val: number) {
console.log(`${val} items per page`);
}
function handleCurrentChange(val: number) {
console.log(`current page: ${val}`);
}
function handleSelectionChange(val) {
console.log("handleSelectionChange", val);
}
async function onSearch() {
loading.value = true;
const { data } = await getRoleList(toRaw(form));
dataList.value = data.list;
pagination.total = data.total;
pagination.pageSize = data.pageSize;
pagination.currentPage = data.currentPage;
setTimeout(() => {
loading.value = false;
}, 500);
}
const resetForm = formEl => {
if (!formEl) return;
formEl.resetFields();
onSearch();
};
function openDialog(title = "新增", row?: FormItemProps) {
addDialog({
title: `${title}角色`,
props: {
formInline: {
name: row?.name ?? "",
code: row?.code ?? "",
remark: row?.remark ?? ""
}
},
width: "40%",
draggable: true,
fullscreenIcon: true,
closeOnClickModal: false,
contentRenderer: () => h(editForm, { ref: formRef }),
beforeSure: (done, { options }) => {
const FormRef = formRef.value.getRef();
const curData = options.props.formInline as FormItemProps;
function chores() {
message(`${title}了角色名称为${curData.name}的这条数据`, {
type: "success"
});
done(); // 关闭弹框
onSearch(); // 刷新表格数据
}
FormRef.validate(valid => {
if (valid) {
console.log("curData", curData);
// 表单规则校验通过
if (title === "新增") {
// 实际开发先调用新增接口,再进行下面操作
chores();
} else {
// 实际开发先调用编辑接口,再进行下面操作
chores();
}
}
});
}
});
}
/** 菜单权限 */
function handleMenu() {
message("等菜单管理页面开发后完善");
}
/** 数据权限 可自行开发 */
// function handleDatabase() {}
onMounted(() => {
onSearch();
});
return {
form,
loading,
columns,
dataList,
pagination,
// buttonClass,
onSearch,
resetForm,
openDialog,
handleMenu,
handleDelete,
// handleDatabase,
handleSizeChange,
handleCurrentChange,
handleSelectionChange
};
}

View File

@ -0,0 +1,8 @@
import { reactive } from "vue";
import type { FormRules } from "element-plus";
/** 自定义表单规则校验 */
export const formRules = reactive(<FormRules>{
name: [{ required: true, message: "角色名称为必填项", trigger: "blur" }],
code: [{ required: true, message: "角色标识为必填项", trigger: "blur" }]
});

View File

@ -0,0 +1,15 @@
// 虽然字段很少 但是抽离出来 后续有扩展字段需求就很方便了
interface FormItemProps {
/** 角色名称 */
name: string;
/** 角色编号 */
code: string;
/** 备注 */
remark: string;
}
interface FormProps {
formInline: FormItemProps;
}
export type { FormItemProps, FormProps };