From 4d75f39fca8c6afbe8a120a329af8b2a59a1cd61 Mon Sep 17 00:00:00 2001 From: zhangwei <1504152376@qq.com> Date: 星期四, 24 七月 2025 17:28:41 +0800 Subject: [PATCH] ‘项目管理页面布局’ --- src/views/system/role/index.vue | 344 ++++ src/views/system/user/svg/expand.svg | 1 src/router/modules/item.ts | 2 src/views/system/role/utils/rule.ts | 8 src/views/system/menu/utils/enums.ts | 108 + src/views/system/hooks.ts | 39 src/views/system/role/utils/hook.tsx | 318 ++++ src/views/system/menu/index.vue | 163 ++ src/views/system/dept/utils/hook.tsx | 311 ++++ src/views/system/dept/utils/types.ts | 38 src/views/system/menu/utils/rule.ts | 10 src/views/system/user/svg/unexpand.svg | 1 src/views/system/user/utils/hook.tsx | 535 +++++++ src/views/system/dept/utils/rule.ts | 37 src/views/system/role/utils/types.ts | 15 src/views/system/menu/utils/hook.tsx | 225 +++ src/views/system/dept/index.vue | 296 ++++ src/views/system/user/form/role.vue | 53 src/views/system/user/utils/rule.ts | 39 src/views/system/user/form/index.vue | 176 ++ src/views/system/role/form.vue | 55 src/api/item/index.ts | 69 src/layout/components/lay-navbar/index.vue | 2 src/views/system/user/tree.vue | 211 +++ src/views/system/user/utils/reset.css | 5 src/views/system/menu/README.md | 26 src/views/system/user/index.vue | 275 +++ src/views/system/menu/form.vue | 342 ++++ src/views/system/user/utils/types.ts | 36 src/views/system/menu/utils/types.ts | 30 src/views/system/dept/form.vue | 403 +++++ 31 files changed, 4,148 insertions(+), 25 deletions(-) diff --git a/src/api/item/index.ts b/src/api/item/index.ts index c2e812a..0689be7 100644 --- a/src/api/item/index.ts +++ b/src/api/item/index.ts @@ -7,10 +7,7 @@ import { http } from "@/utils/http"; import { baseUrlApi } from "../util"; -type Result = { - success: boolean; - data: Array<any>; -}; +import type { Result } from "../types"; // 鑾峰彇琛屾斂鍖哄煙鍒楄〃 export const getRegionList = () => { @@ -96,53 +93,79 @@ // 棣栭〉鏌ヨ闈炴斂搴滆鍗曞鐞� export const shouyeOrder = (data?: object) => { - return http.request("post", baseUrlApi("/api/tenderOrder/shouyeOrder"), { - data - }); + return http.request<Result>( + "post", + baseUrlApi("/api/tenderOrder/shouyeOrder"), + { + data + } + ); }; // 鎷涙爣浠g悊鍒嗛〉鏌ヨ闈炴斂搴滆鍗曞鐞� export const zhaobiaoPageOrder = (data?: object) => { - return http.request("post", baseUrlApi("/api/tenderOrder/page"), { data }); + return http.request<Result>("post", baseUrlApi("/api/tenderOrder/page"), { + data + }); }; // 閲囪喘浠g悊浜哄鍔犻潪鏀垮簻璁㈠崟澶勭悊 export const caigourenAdd = (data?: object) => { - return http.request("post", baseUrlApi("/api/tenderOrder/add"), { data }); + return http.request<Result>("post", baseUrlApi("/api/tenderOrder/add"), { + data + }); }; // 閲囪喘浠g悊浜烘洿鏂伴潪鏀垮簻璁㈠崟澶勭悊 export const caigourenUpdate = (data?: object) => { - return http.request("post", baseUrlApi("/api/tenderOrder/update"), { data }); + return http.request<Result>("post", baseUrlApi("/api/tenderOrder/update"), { + data + }); }; // 閲囪喘浠g悊浜烘洿鏂伴潪鏀垮簻璁㈠崟璐ㄧ枒 export const caigourenUpdateZhiyi = (data?: object) => { - return http.request("post", baseUrlApi("/api/tenderOrder/updateZhiyi"), { - data - }); + return http.request<Result>( + "post", + baseUrlApi("/api/tenderOrder/updateZhiyi"), + { + data + } + ); }; // 閲囪喘浠g悊浜烘洿鏂伴潪鏀垮簻璁㈠崟鎶曡瘔 export const caigourenUpdateTousu = (data?: object) => { - return http.request("post", baseUrlApi("/api/tenderOrder/updateTousu"), { - data - }); + return http.request<Result>( + "post", + baseUrlApi("/api/tenderOrder/updateTousu"), + { + data + } + ); }; // 閲囪喘浠g悊浜哄垹闄ら潪鏀垮簻璁㈠崟澶勭悊 export const caigourenDelete = (data?: object) => { - return http.request("post", baseUrlApi("/api/tenderOrder/delete"), { data }); -}; - -// 閲囪喘浠g悊浜烘壒閲忓垹闄ら潪鏀垮簻璁㈠崟澶勭悊 -export const caigourenBatchDelete = (data?: object) => { - return http.request("post", baseUrlApi("/api/tenderOrder/batchDelete"), { + return http.request<Result>("post", baseUrlApi("/api/tenderOrder/delete"), { data }); }; +// 閲囪喘浠g悊浜烘壒閲忓垹闄ら潪鏀垮簻璁㈠崟澶勭悊 +export const caigourenBatchDelete = (data?: object) => { + return http.request<Result>( + "post", + baseUrlApi("/api/tenderOrder/batchDelete"), + { + data + } + ); +}; + // 瀵煎嚭闈炴斂搴滆鍗曞鐞嗚褰� export const exportFZF = (data?: object) => { - return http.request("post", baseUrlApi("/api/tenderOrder/export"), { data }); + return http.request<Result>("post", baseUrlApi("/api/tenderOrder/export"), { + data + }); }; diff --git a/src/layout/components/lay-navbar/index.vue b/src/layout/components/lay-navbar/index.vue index 3a83f47..f5bc706 100644 --- a/src/layout/components/lay-navbar/index.vue +++ b/src/layout/components/lay-navbar/index.vue @@ -53,7 +53,7 @@ <span class="el-dropdown-link navbar-bg-hover select-none"> <!-- <img :src="userAvatar" :style="avatarsStyle" /> --> <p class="dark:text-white"> - {{ username || "璇峰畬鍠勮祫鏂�" }} + 浣犲ソ锛寋{ username || "璇峰畬鍠勮祫鏂�" }} <el-tag effect="plain">{{ userRoles.name }}</el-tag> </p> <el-icon><CaretBottom /></el-icon> diff --git a/src/router/modules/item.ts b/src/router/modules/item.ts index 768d651..3d2aa47 100644 --- a/src/router/modules/item.ts +++ b/src/router/modules/item.ts @@ -1,6 +1,6 @@ export default { path: "/item", - component: () => import("@/views/item/index.vue"), + component: () => import("@/views/system/dept/index.vue"), name: "item", // redirect: "/error/403", meta: { diff --git a/src/views/system/dept/form.vue b/src/views/system/dept/form.vue new file mode 100644 index 0000000..98fd3eb --- /dev/null +++ b/src/views/system/dept/form.vue @@ -0,0 +1,403 @@ +<script setup lang="ts"> +import { onMounted, ref, reactive } from "vue"; +import ReCol from "@/components/ReCol"; +import { formRules } from "./utils/rule"; +import { FormProps } from "./utils/types"; +import { usePublicHooks } from "../hooks"; +import { useDept } from "./utils/hook"; +import { Operation } from "@element-plus/icons-vue"; +import { getCaigoufangshiList } from "@/api/item/index"; + +const { state } = useDept(); +const props = withDefaults(defineProps<FormProps>(), { + formInline: () => ({ + projectCode: "", // 椤圭洰缂栧彿锛堝繀濉級 + projectName: "", // 椤圭洰鍚嶇О锛堝繀濉級 + hangyepinmu: null, // 琛屼笟鍝佺洰锛堝彲閫夛級 + caigoufangshi: null, // 閲囪喘鏂瑰紡锛堝彲閫夛級 + caigouyusuan: null, // 閲囪喘棰勭畻锛堝彲閫夛級 + dingbiaoguize: null, // 瀹氭爣瑙勫垯锛堝彲閫夛級 + baomingfei: null, // 鎶ュ悕璐癸紙鍙�夛級 + toubiaobaozhengjin: null, // 鎶曟爣淇濊瘉閲戯紙鍙�夛級 + lianhetitoubiao: null, // 鑱斿悎浣撴姇鏍囷紙鍙�夛級 + kaibiaofangshi: null, // 寮�鏍囨柟寮忥紙鍙�夛級 + shifoufenbao: "false", // 鏄惁鍒嗗寘锛堝彲閫夛級 + shifoutuisongxuanchuan: "true", // 鏄惁鎺ㄩ�佸浼狅紙鍙�夛級 + caigourenmingcheng: null, // 閲囪喘浜哄悕绉帮紙鍙�夛級 + xingzhengquyu: null, // 琛屾斂鍖哄煙锛堝彲閫夛級 + xingzhengquyuName: null, // 琛屾斂鍖哄煙鍚嶇О锛堝彲閫夛級 + jigoudaima: null, // 鏈烘瀯浠g爜锛堝彲閫夛級 + daimaleixing: null, // 浠g爜绫诲瀷锛堝彲閫夛級 + lianxiren: null, // 鑱旂郴浜猴紙鍙�夛級 + lianxidianhua: null, // 鑱旂郴鐢佃瘽锛堝彲閫夛級 + tongxindizhi: null, // 閫氫俊鍦板潃锛堝彲閫夛級 + dianziyoujian: null, // 鐢靛瓙閭欢锛堝彲閫夛級 + xiangmujingbanren: null, // 椤圭洰缁忓姙浜猴紙鍙�夛級 + zhiwu: null, // 鑱屽姟锛堝彲閫夛級 + jingbanrendianhua: null, // 缁忓姙浜虹數璇濓紙鍙�夛級 + dailijigoumingcheng: null, // 浠g悊鏈烘瀯鍚嶇О锛堝彲閫夛級 + dailiLianxiren: null, // 浠g悊鏈烘瀯鑱旂郴浜猴紙鍙�夛級 + dailiLianxidianhua: null, // 浠g悊鏈烘瀯鑱旂郴鐢佃瘽锛堝彲閫夛級 + dailiDianziyoujian: null, // 浠g悊鏈烘瀯鐢靛瓙閭欢锛堝彲閫夛級 + dailiTongxindizhi: null, // 浠g悊鏈烘瀯閫氫俊鍦板潃锛堝彲閫夛級 + dailiXiangmujingli: null, // 浠g悊鏈烘瀯椤圭洰缁忕悊锛堝彲閫� + dailijingliLianxidianhua: null // 浠g悊鏈烘瀯椤圭洰缁忕悊鑱旂郴鐢佃瘽锛堝彲閫夛級 + }) +}); + +const ruleFormRef = ref(); +const { switchStyle } = usePublicHooks(); +const newFormInline = ref(props.formInline); + +function getRef() { + return ruleFormRef.value; +} + +defineExpose({ getRef }); +onMounted(async () => {}); +</script> + +<template> + <el-form + ref="ruleFormRef" + :model="newFormInline" + :rules="formRules" + label-width="110px" + > + <el-row :gutter="30"> + <re-col> + <p class="flex items-center"> + <el-icon color="#409EFF" class="m-2" size="large"> + <Operation /> + </el-icon> + <el-text class="mx-1" size="large" type="primary" tag="b"> + 椤圭洰淇℃伅 + </el-text> + </p> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="椤圭洰鍚嶇О" prop="projectName"> + <el-input + v-model="newFormInline.projectName" + clearable + placeholder="璇疯緭鍏ラ」鐩悕绉�" + /> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="椤圭洰缂栧彿" prop="projectCode"> + <el-input + v-model="newFormInline.projectCode" + clearable + placeholder="璇疯緭鍏ラ」鐩紪鍙�" + /> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="琛屼笟鍝佺洰" prop="hangyepinmu"> + <el-select + v-model="newFormInline.hangyepinmu" + placeholder="璇烽�夋嫨琛屼笟鍝佺洰" + clearable + class="w-[100%]!" + > + <el-option + v-for="item in state.hangyepingmuList" + :key="item.id" + :label="item.label" + :value="item.value" + /> + </el-select> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="閲囪喘鏂瑰紡"> + <el-select + v-model="newFormInline.caigoufangshi" + placeholder="璇烽�夋嫨閲囪喘鏂瑰紡" + style="width: 240px" + > + <el-option + v-for="item in state.caigoufangshiList" + :key="item.value" + :label="item.label" + :value="item.value" + /> + </el-select> + </el-form-item> + </re-col> + + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="閲囪喘棰勭畻" prop="caigouyusuan"> + <el-input + v-model="newFormInline.caigouyusuan" + clearable + placeholder="璇疯緭鍏ラ噰璐绠�" + /> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="瀹氬埗瑙勫垝" prop="dingbiaoguize"> + <el-checkbox-group + v-model="newFormInline.dingbiaoguize" + placeholder="璇烽�夋嫨鐘舵��" + clearable + class="w-[100%]!" + > + <el-checkbox label="鏈�浣庝环" :value="1" /> + <el-checkbox label="缁煎悎璇勫垎" :value="0" /> + </el-checkbox-group> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="鎶ュ悕璐�" prop="baomingfei"> + <el-input + v-model="newFormInline.baomingfei" + clearable + placeholder="璇疯緭鍏ユ姤鍚嶈垂" + /> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="鎶曟爣淇濊瘉閲�" prop="toubiaobaozhengjin"> + <el-input + v-model="newFormInline.toubiaobaozhengjin" + clearable + placeholder="璇疯緭鍏ユ姇鏍囦繚璇侀噾" + /> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="鑱斿悎浣撴姇鏍�" prop="lianhetitoubiao"> + <el-radio-group v-model="newFormInline.lianhetitoubiao"> + <el-radio value="鏀寔">鏀寔</el-radio> + <el-radio value="涓嶆敮鎸�">涓嶆敮鎸�</el-radio> + </el-radio-group> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="寮�鏍囨柟寮�" prop="kaibiaofangshi"> + <el-radio-group v-model="newFormInline.kaibiaofangshi"> + <el-radio value="绾歌川鏍�">绾歌川鏍�</el-radio> + <el-radio value="鐢靛瓙鏍�">鐢靛瓙鏍�</el-radio> + </el-radio-group> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="鏄惁鍒嗗寘" prop="shifoufenbao"> + <el-radio-group v-model="newFormInline.shifoufenbao"> + <el-radio value="true">鏄�</el-radio> + <el-radio value="false">鍚�</el-radio> + </el-radio-group> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="鏄惁鎺ㄩ�佸浼�" prop="shifoutuisongxuanchuan"> + <el-radio-group v-model="newFormInline.shifoutuisongxuanchuan"> + <el-radio value="true">鏄�</el-radio> + <el-radio value="false">鍚�</el-radio> + </el-radio-group> + </el-form-item> + </re-col> + + <re-col> + <p class="flex items-center"> + <el-icon color="#409EFF" class="m-2" size="large"> + <Operation /> + </el-icon> + <el-text class="mx-1" size="large" type="primary" tag="b"> + 閲囪喘浜轰俊鎭� + </el-text> + </p> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="閲囪喘浜哄悕绉�" prop="caigourenmingcheng"> + <el-input + v-model="newFormInline.caigourenmingcheng" + clearable + placeholder="璇疯緭鍏ラ噰璐汉鍚嶇О" + /> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="琛屾斂鍖哄煙" prop="xingzhengquyu"> + <el-cascader + v-model="newFormInline.xingzhengquyu" + class="w-full" + :options="state.regionList" + :props="{ + value: 'id', + label: 'name', + emitPath: false, + children: 'regions' + }" + clearable + filterable + placeholder="璇烽�夋嫨鍖哄煙" + /> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="鏈烘瀯浠g爜" prop="jigoudaima"> + <el-input + v-model="newFormInline.jigoudaima" + clearable + placeholder="璇疯緭鍏ユ満鏋勪唬鐮�" + /> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="浠g爜绫诲瀷" prop="daimaleixing"> + <el-select + v-model="newFormInline.daimaleixing" + placeholder="璇烽�夋嫨浠g爜绫诲瀷" + style="width: 240px" + > + <el-option + v-for="item in state.daimaleixingList" + :key="item.value" + :label="item.label" + :value="item.value" + /> + </el-select> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="鑱旂郴浜�" prop="lianxiren"> + <el-input + v-model="newFormInline.lianxiren" + clearable + placeholder="璇疯緭鍏ヨ仈绯讳汉" + /> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="鑱旂郴浜虹數璇�" prop="lianxidianhua"> + <el-input + v-model="newFormInline.lianxidianhua" + clearable + placeholder="璇疯緭鍏ヨ仈绯讳汉鐢佃瘽" + /> + </el-form-item> + </re-col> + <re-col :value="12" :xs="24" :sm="24"> + <el-form-item label="閫氫俊鍦板潃" prop="tongxindizhi"> + <el-input + v-model="newFormInline.tongxindizhi" + clearable + placeholder="璇疯緭鍏ラ�氫俊鍦板潃" + /> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="鐢靛瓙閭" prop="dianziyoujian"> + <el-input + v-model="newFormInline.dianziyoujian" + clearable + placeholder="璇疯緭鍏ョ數瀛愰偖绠�" + /> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="椤圭洰缁忓姙浜�" prop="xiangmujingbanren"> + <el-input + v-model="newFormInline.xiangmujingbanren" + clearable + placeholder="璇疯緭鍏ラ」鐩粡鍔炰汉" + /> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="鑱屽姟" prop="zhiwu"> + <el-input + v-model="newFormInline.zhiwu" + clearable + placeholder="璇疯緭鍏ヨ亴鍔�" + /> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="缁忓姙浜虹數璇�" prop="jingbanrendianhua"> + <el-input + v-model="newFormInline.jingbanrendianhua" + clearable + placeholder="璇疯緭鍏ョ粡鍔炰汉鐢佃瘽" + /> + </el-form-item> + </re-col> + <re-col> + <p class="flex items-center"> + <el-icon color="#409EFF" class="m-2" size="large"> + <Operation /> + </el-icon> + <el-text class="mx-1" size="large" type="primary" tag="b"> + 浠g悊鏈烘瀯淇℃伅 + </el-text> + </p> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="浠g悊鏈烘瀯鍚嶇О" prop="dailijigoumingcheng"> + <el-input + v-model="newFormInline.dailijigoumingcheng" + clearable + placeholder="璇疯緭鍏ヤ唬鐞嗘満鏋勫悕绉�" + /> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="鑱旂郴浜轰汉" prop="dailiLianxiren"> + <el-input + v-model="newFormInline.dailiLianxiren" + clearable + placeholder="璇疯緭鍏ヨ仈绯讳汉" + /> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="鑱旂郴鐢佃瘽" prop="dailiLianxidianhua"> + <el-input + v-model="newFormInline.dailiLianxidianhua" + clearable + placeholder="璇疯緭鍏ヨ仈绯荤數璇�" + /> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="鐢靛瓙閭" prop="dailiDianziyoujian"> + <el-input + v-model="newFormInline.dailiDianziyoujian" + clearable + placeholder="璇疯緭鍏ョ數瀛愰偖绠�" + /> + </el-form-item> + </re-col> + <re-col :value="12" :xs="24" :sm="24"> + <el-form-item label="閫氫俊鍦板潃" prop="dailiTongxindizhi"> + <el-input + v-model="newFormInline.dailiTongxindizhi" + clearable + placeholder="璇疯緭鍏ラ�氫俊鍦板潃" + /> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="椤圭洰缁忕悊" prop="dailiXiangmujingli"> + <el-input + v-model="newFormInline.dailiXiangmujingli" + clearable + placeholder="璇疯緭鍏ラ」鐩粡鐞�" + /> + </el-form-item> + </re-col> + <re-col :value="6" :xs="24" :sm="24"> + <el-form-item label="鑱旂郴鐢佃瘽" prop="dailijingliLianxidianhua"> + <el-input + v-model="newFormInline.dailijingliLianxidianhua" + clearable + placeholder="璇疯緭鍏ヨ仈绯荤數璇�" + /> + </el-form-item> + </re-col> + </el-row> + </el-form> +</template> diff --git a/src/views/system/dept/index.vue b/src/views/system/dept/index.vue new file mode 100644 index 0000000..04b15f4 --- /dev/null +++ b/src/views/system/dept/index.vue @@ -0,0 +1,296 @@ +<script setup lang="ts"> +import { ref, reactive, onMounted } from "vue"; +import { useDept } from "./utils/hook"; +import { PureTableBar } from "@/components/RePureTableBar"; +import { useRenderIcon } from "@/components/ReIcon/src/hooks"; + +import Delete from "~icons/ep/delete"; +import EditPen from "~icons/ep/edit-pen"; +import Refresh from "~icons/ep/refresh"; +import AddFill from "~icons/ri/add-circle-line"; + +defineOptions({ + name: "SystemDept" +}); + +const formRef = ref(); +const tableRef = ref(); +const { + form, + state, + loading, + columns, + dataList, + onSearch, + resetForm, + openDialog, + handleDelete, + handleSelectionChange +} = useDept(); + +function onFullscreen() { + // 閲嶇疆琛ㄦ牸楂樺害 + tableRef.value.setAdaptive(); +} + +onMounted(() => {}); +</script> + +<template> + <div class="main"> + <!-- class="search-form bg-bg_color w-full pl-8 pt-[12px] overflow-auto" --> + <el-card shadow="hover" :body-style="{ paddingBottom: '0' }"> + <el-form ref="formRef" :model="form" labelWidth="100"> + <el-row> + <el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6"> + <el-form-item label="鏃堕棿锛�"> + <el-date-picker + v-model="form.createDateRange" + type="daterange" + start-placeholder="寮�濮嬫棩鏈�" + end-placeholder="缁撴潫鏃ユ湡" + value-format="YYYY-MM-DD HH:mm:ss" + :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" + class="w-[100%]!" + /> + <!-- start-placeholder="寮�濮嬫棩鏈�" + end-placeholder="缁撴潫鏃ユ湡" --> + </el-form-item> + </el-col> + <el-col :xs="24" :sm="12" :md="12" :lg="6" :xl="6"> + <el-form-item label="鍖哄煙锛�"> + <el-cascader + v-model="form.xingzhengquyu" + class="w-full" + :options="state.regionList" + :props="{ + value: 'id', + label: 'name', + emitPath: false, + children: 'regions' + }" + clearable + filterable + placeholder="璇烽�夋嫨鍖哄煙" + /> + </el-form-item> + </el-col> + <el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4"> + <el-form-item label="琛屼笟鍝佺洰锛�"> + <el-select + v-model="form.hangyepinmu" + placeholder="璇烽�夋嫨琛屼笟鍝佺洰" + clearable + class="w-[100%]!" + > + <el-option + v-for="item in state.hangyepingmuList" + :key="item.id" + :label="item.label" + :value="item.value" + /> + </el-select> + </el-form-item> + </el-col> + <el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4"> + <el-form-item label="椤圭洰杩涘害锛�"> + <el-select + v-model="form.orderStatus" + placeholder="璇烽�夋嫨椤圭洰杩涘害" + clearable + class="w-[100%]!" + > + <el-option + v-for="item in state.orderStatusList" + :key="item.id" + :label="item.label" + :value="item.value" + /> + </el-select> + </el-form-item> </el-col + ><el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4"> + <el-form-item label="璐ㄧ枒锛�"> + <el-checkbox-group + v-model="form.zhiyi" + clearable + class="w-[100%]!" + > + <el-checkbox label="鏈�" :value="1" /> + <el-checkbox label="鏃�" :value="0" /> + </el-checkbox-group> + </el-form-item> + </el-col> + <el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4"> + <el-form-item label="鎶曡瘔锛�"> + <el-checkbox-group + v-model="form.tousu" + clearable + class="w-[100%]!" + > + <el-checkbox label="鏈�" :value="1" /> + <el-checkbox label="鏃�" :value="0" /> + </el-checkbox-group> + </el-form-item> + </el-col> + <el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4"> + <el-form-item label="椤圭洰鍚嶇О锛�"> + <el-input + v-model="form.projectName" + placeholder="璇疯緭鍏ラ」鐩悕绉�" + clearable + class="w-[100%]!" + /> + </el-form-item> + </el-col> + <el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4"> + <el-form-item label="浠g悊鏈烘瀯锛�"> + <el-input + v-model="form.dailijigoumingcheng" + placeholder="璇疯緭鍏ヤ唬鐞嗘満鏋�" + clearable + class="w-[100%]!" + /> + </el-form-item> + </el-col> + <el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4"> + <el-form-item label="涓爣渚涘簲鍟嗭細"> + <el-input + v-model="form.zhongbiaoName" + placeholder="璇疯緭鍏ヤ腑鏍囦緵搴斿晢" + clearable + class="w-[100%]!" + /> + </el-form-item> + </el-col> + <el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4"> + <el-form-item label="璇勫涓撳锛�"> + <el-input + v-model="form.zhuanjiaName" + placeholder="璇疯緭鍏ヨ瘎瀹′笓瀹�" + clearable + class="w-[100%]!" + /> + </el-form-item> + </el-col> + <el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4"> + <el-form-item label-width="40"> + <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-button + type="primary" + :icon="useRenderIcon(AddFill)" + @click="openDialog()" + > + 鏂板 + </el-button> + </el-form-item> + </el-col> + </el-row> + </el-form> + </el-card> + <PureTableBar + title="" + :columns="columns" + :tableRef="tableRef?.getTableRef()" + @refresh="onSearch" + @fullscreen="onFullscreen" + > + <!-- <template #buttons> + <el-button + type="primary" + :icon="useRenderIcon(AddFill)" + @click="openDialog()" + > + 鏂板椤圭洰 + </el-button> + </template> --> + <template v-slot="{ size, dynamicColumns }"> + <pure-table + ref="tableRef" + adaptive + :adaptiveConfig="{ offsetBottom: 45 }" + align-whole="center" + row-key="id" + showOverflowTooltip + table-layout="auto" + default-expand-all + :loading="loading" + :data="dataList" + :columns="dynamicColumns" + :header-cell-style="{ + background: 'var(--el-fill-color-light)', + color: 'var(--el-text-color-primary)' + }" + @selection-change="handleSelectionChange" + > + <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(AddFill)" + @click="openDialog('鏂板', { parentId: row.id } as any)" + > + 鏂板 + </el-button> --> + <el-popconfirm + :title="`鏄惁纭鍒犻櫎閮ㄩ棬鍚嶇О涓�${row.projectName}鐨勮繖鏉℃暟鎹甡" + @confirm="handleDelete(row)" + > + <template #reference> + <el-button + class="reset-margin" + link + type="primary" + :size="size" + :icon="useRenderIcon(Delete)" + > + 鍒犻櫎 + </el-button> + </template> + </el-popconfirm> + </template> + </pure-table> + </template> + </PureTableBar> + </div> +</template> + +<style lang="scss" scoped> +:deep(.el-table__inner-wrapper::before) { + height: 0; +} + +.main-content { + margin: 24px 24px 0 !important; +} + +.search-form { + :deep(.el-form-item) { + margin-bottom: 12px; + } +} +</style> diff --git a/src/views/system/dept/utils/hook.tsx b/src/views/system/dept/utils/hook.tsx new file mode 100644 index 0000000..d11ee6f --- /dev/null +++ b/src/views/system/dept/utils/hook.tsx @@ -0,0 +1,311 @@ +import dayjs from "dayjs"; +import editForm from "../form.vue"; +import { handleTree } from "@/utils/tree"; +import { message } from "@/utils/message"; +import { zhaobiaoPageOrder } from "@/api/item/index"; + +import { + getCaigoufangshiList, + getHangyepingmuList, + getOrderStatusList, + getRegionList, + getDaimaleixingList, + caigourenAdd, + caigourenDelete +} from "@/api/item/index"; +import { usePublicHooks } from "../../hooks"; +import { addDialog } from "@/components/ReDialog"; +import { reactive, ref, onMounted, h } from "vue"; +import type { FormItemProps } from "../utils/types"; +import { cloneDeep, isAllEmpty, deviceDetection } from "@pureadmin/utils"; + +export function useDept() { + const form = reactive({ + // 鏂板鏃ユ湡鑼冨洿锛屽彲涓� null锛岀被鍨嬩负鏁扮粍 + createDateRange: null, + // 琛屾斂鍖哄煙锛屽彲涓� null锛岀被鍨嬩负瀛楃涓� + xingzhengquyu: "", + // 琛屼笟鍝佺洰锛屽彲涓� null锛岀被鍨嬩负瀛楃涓� + hangyepinmu: "", + // 璁㈠崟鐘舵�侊紝鍙负 null锛岀被鍨嬩负 32 浣嶆暣鏁� + orderStatus: "", + // 璐ㄧ枒鏈夋棤锛屽彲涓� null锛岀被鍨嬩负甯冨皵鍊� + zhiyi: null, + // 鎶曡瘔鏈夋棤锛屽彲涓� null锛岀被鍨嬩负甯冨皵鍊� + tousu: null, + // 椤圭洰鍚嶇О锛屽彲涓� null锛岀被鍨嬩负瀛楃涓� + projectName: null, + // 浠g悊鏈烘瀯鍚嶇О锛屽彲涓� null锛岀被鍨嬩负瀛楃涓� + dailijigoumingcheng: null, + // 涓爣渚涘簲鍟嗗鍚嶏紝鍙负 null锛岀被鍨嬩负瀛楃涓� + zhongbiaoName: null, + // 涓撳濮撳悕锛屽彲涓� null锛岀被鍨嬩负瀛楃涓� + zhuanjiaName: null + }); + const state = reactive({ + caigoufangshiList: [], + hangyepingmuList: [], + orderStatusList: [], + regionList: [], + daimaleixingList: [] + }); + //鑾峰彇閲囪喘鏂瑰紡 + const getCaigoufangshiListFun = async () => { + const res = await getCaigoufangshiList(); + state.caigoufangshiList = res.result; + }; + + //鑾峰彇琛屼笟鍝佺洰 + const getHangyepingmuListFun = async () => { + const res = await getHangyepingmuList(); + state.hangyepingmuList = res.result; + }; + + //鑾峰彇椤圭洰杩涘害 + const getOrderStatusListFun = async () => { + const res = await getOrderStatusList(); + state.orderStatusList = res.result; + }; + + // 鑾峰彇鍖哄煙 + const getRegionListFun = async () => { + const res = await getRegionList(); + state.regionList = res.result; + }; + + // 鑾峰彇浠g爜绫诲瀷 + const getDaimaleixingListFun = async () => { + const res = await getDaimaleixingList(); + state.daimaleixingList = res.result; + }; + const formRef = ref(); + const dataList = ref([]); + const loading = ref(true); + const { tagStyle } = usePublicHooks(); + const getOrderStatus = row => { + const res = state.orderStatusList.find(item => { + return row.orderStatus == item.status; + }); + return res.label; + }; + const columns: TableColumnList = [ + { + label: "椤圭洰鍚嶇О", + prop: "projectName", + width: 180, + align: "left" + }, + { + label: "浠g悊鏈烘瀯", + prop: "dailijigoumingcheng", + minWidth: 70 + }, + { + label: "椤圭洰杩涘害", + prop: "orderStatus", + minWidth: 70, + cellRenderer: ({ row, props }) => getOrderStatus(row) + }, + { + label: "鎶ュ悕璐�", + prop: "baomingfei", + minWidth: 70 + }, + { + label: "鎶曟爣淇濊瘉閲�", + prop: "toubiaobaozhengjin", + minWidth: 70 + }, + { + label: "涓爣渚涘簲鍟�", + prop: "zhongbiaoName", + minWidth: 70 + }, + { + label: "璇勫涓撳", + prop: "zhuanjiaName", + minWidth: 70 + }, + { + label: "璐ㄧ枒", + prop: "zhiyi", + minWidth: 100, + cellRenderer: ({ row, props }) => ( + <span>{row.status === 1 ? "鏈�" : "鏃�"}</span> + ) + }, + { + label: "鎶曡瘔", + prop: "tousu", + minWidth: 100, + cellRenderer: ({ row, props }) => ( + // <el-tag size={props.size} style={tagStyle.value(row.status)}> + <span>{row.status === 1 ? "鏈�" : "鏃�"}</span> + // </el-tag> + ) + }, + { + label: "鎿嶄綔", + fixed: "right", + width: 210, + slot: "operation" + } + ]; + + function handleSelectionChange(val) { + console.log("handleSelectionChange", val); + } + + function resetForm(formEl) { + if (!formEl) return; + formEl.resetFields(); + onSearch(); + } + + async function onSearch() { + loading.value = true; + const { result } = await zhaobiaoPageOrder(form); // 杩欓噷鏄繑鍥炰竴缁存暟缁勭粨鏋勶紝鍓嶇鑷澶勭悊鎴愭爲缁撴瀯锛岃繑鍥炴牸寮忚姹傦細鍞竴id鍔犵埗鑺傜偣parentId锛宲arentId鍙栫埗鑺傜偣id + const newData = result.items; + loading.value = false; + // if (!isAllEmpty(form.name)) { + // // 鍓嶇鎼滅储閮ㄩ棬鍚嶇О + // newData = newData.filter(item => item.name.includes(form.name)); + // } + // if (!isAllEmpty(form.status)) { + // // 鍓嶇鎼滅储鐘舵�� + // newData = newData.filter(item => item.status === form.status); + // } + dataList.value = handleTree(newData); // 澶勭悊鎴愭爲缁撴瀯 + } + + function formatHigherDeptOptions(treeList) { + // 鏍规嵁杩斿洖鏁版嵁鐨剆tatus瀛楁鍊煎垽鏂拷鍔犳槸鍚︾鐢╠isabled瀛楁锛岃繑鍥炲鐞嗗悗鐨勬爲缁撴瀯锛岀敤浜庝笂绾ч儴闂ㄧ骇鑱旈�夋嫨鍣ㄧ殑灞曠ず锛堝疄闄呭紑鍙戜腑涔熸槸濡傛锛屼笉鍙兘鍓嶇闇�瑕佺殑姣忎釜瀛楁鍚庣閮戒細杩斿洖锛岃繖鏃堕渶瑕佸墠绔嚜琛屾牴鎹悗绔繑鍥炵殑鏌愪簺瀛楁鍋氶�昏緫澶勭悊锛� + if (!treeList || !treeList.length) return; + const newTreeList = []; + for (let i = 0; i < treeList.length; i++) { + treeList[i].disabled = treeList[i].status === 0 ? true : false; + formatHigherDeptOptions(treeList[i].children); + newTreeList.push(treeList[i]); + } + return newTreeList; + } + + function openDialog(title = "鏂板", row?: FormItemProps) { + addDialog({ + title: `${title}椤圭洰`, + props: { + formInline: { + higherDeptOptions: formatHigherDeptOptions(cloneDeep(dataList.value)), + projectCode: row?.projectCode ?? "", // 椤圭洰缂栧彿锛堝繀濉級 + projectName: row?.projectName ?? "", // 椤圭洰鍚嶇О锛堝繀濉級 + hangyepinmu: row?.hangyepinmu ?? null, // 琛屼笟鍝佺洰锛堝彲閫夛級 + caigoufangshi: row?.caigoufangshi ?? null, // 閲囪喘鏂瑰紡锛堝彲閫夛級 + caigouyusuan: row?.caigouyusuan ?? null, // 閲囪喘棰勭畻锛堝彲閫夛級 + dingbiaoguize: row?.dingbiaoguize ?? null, // 瀹氭爣瑙勫垯锛堝彲閫夛級 + baomingfei: row?.baomingfei ?? null, // 鎶ュ悕璐癸紙鍙�夛級 + toubiaobaozhengjin: row?.toubiaobaozhengjin ?? null, // 鎶曟爣淇濊瘉閲戯紙鍙�夛級 + lianhetitoubiao: row?.lianhetitoubiao ?? null, // 鑱斿悎浣撴姇鏍囷紙鍙�夛級 + kaibiaofangshi: row?.kaibiaofangshi ?? null, // 寮�鏍囨柟寮忥紙鍙�夛級 + shifoufenbao: row?.shifoufenbao ?? false, // 鏄惁鍒嗗寘锛堝彲閫夛級 + shifoutuisongxuanchuan: row?.shifoutuisongxuanchuan ?? true, // 鏄惁鎺ㄩ�佸浼狅紙鍙�夛級 + caigourenmingcheng: row?.caigourenmingcheng ?? null, // 閲囪喘浜哄悕绉帮紙鍙�夛級 + xingzhengquyu: row?.xingzhengquyu ?? null, // 琛屾斂鍖哄煙锛堝彲閫夛級 + xingzhengquyuName: row?.xingzhengquyuName ?? null, // 琛屾斂鍖哄煙鍚嶇О锛堝彲閫夛級 + jigoudaima: row?.jigoudaima ?? null, // 鏈烘瀯浠g爜锛堝彲閫夛級 + daimaleixing: row?.daimaleixing ?? null, // 浠g爜绫诲瀷锛堝彲閫夛級 + lianxiren: row?.lianxiren ?? null, // 鑱旂郴浜猴紙鍙�夛級 + lianxidianhua: row?.lianxidianhua ?? null, // 鑱旂郴鐢佃瘽锛堝彲閫夛級 + tongxindizhi: row?.tongxindizhi ?? null, // 閫氫俊鍦板潃锛堝彲閫夛級 + dianziyoujian: row?.dianziyoujian ?? null, // 鐢靛瓙閭欢锛堝彲閫夛級 + xiangmujingbanren: row?.xiangmujingbanren ?? null, // 椤圭洰缁忓姙浜猴紙鍙�夛級 + zhiwu: row?.zhiwu ?? null, // 鑱屽姟锛堝彲閫夛級 + jingbanrendianhua: row?.jingbanrendianhua ?? null, // 缁忓姙浜虹數璇濓紙鍙�夛級 + dailijigoumingcheng: row?.dailijigoumingcheng ?? null, // 浠g悊鏈烘瀯鍚嶇О锛堝彲閫夛級 + dailiLianxiren: row?.dailiLianxiren ?? null, // 浠g悊鏈烘瀯鑱旂郴浜猴紙鍙�夛級 + dailiLianxidianhua: row?.dailiLianxidianhua ?? null, // 浠g悊鏈烘瀯鑱旂郴鐢佃瘽锛堝彲閫夛級 + dailiDianziyoujian: row?.dailiDianziyoujian ?? null, // 浠g悊鏈烘瀯鐢靛瓙閭欢锛堝彲閫夛級 + dailiTongxindizhi: row?.dailiTongxindizhi ?? null, // 浠g悊鏈烘瀯閫氫俊鍦板潃锛堝彲閫夛級 + dailiXiangmujingli: row?.dailiXiangmujingli ?? null, // 浠g悊鏈烘瀯椤圭洰缁忕悊锛堝彲閫� + dailijingliLianxidianhua: row?.dailijingliLianxidianhua ?? null // 浠g悊鏈烘瀯椤圭洰缁忕悊鑱旂郴鐢佃瘽锛堝彲閫夛級 + } + }, + width: "80%", + 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; + async function chores() { + message(`鎮�${title}浜嗛」鐩悕绉颁负${curData.projectName}鐨勮繖鏉℃暟鎹甡, { + type: "success" + }); + const res = await caigourenAdd(curData); + if (res.code == "200") { + done(); // 鍏抽棴寮规 + onSearch(); // 鍒锋柊琛ㄦ牸鏁版嵁 + } else { + message(res.message, { + type: "error" + }); + } + } + FormRef.validate(valid => { + if (valid) { + console.log("curData", curData); + // 琛ㄥ崟瑙勫垯鏍¢獙閫氳繃 + if (title === "鏂板") { + // 瀹為檯寮�鍙戝厛璋冪敤鏂板鎺ュ彛锛屽啀杩涜涓嬮潰鎿嶄綔 + chores(); + } else { + // 瀹為檯寮�鍙戝厛璋冪敤淇敼鎺ュ彛锛屽啀杩涜涓嬮潰鎿嶄綔 + chores(); + } + } + }); + } + }); + } + + async function handleDelete(row) { + const res = await caigourenDelete({ id: row.id }); + if (res.code == "200") { + message(`鎮ㄥ垹闄や簡椤圭洰鍚嶇О涓�${row.projectName}鐨勮繖鏉℃暟鎹甡, { + type: "success" + }); + onSearch(); + } else { + message(res.message, { + type: "error" + }); + } + } + + onMounted(() => { + onSearch(); + getCaigoufangshiListFun(); + getHangyepingmuListFun(); + getOrderStatusListFun(); + getRegionListFun(); + getDaimaleixingListFun(); + }); + + return { + form, + state, + loading, + columns, + dataList, + /** 鎼滅储 */ + onSearch, + /** 閲嶇疆 */ + resetForm, + /** 鏂板銆佷慨鏀归儴闂� */ + openDialog, + /** 鍒犻櫎閮ㄩ棬 */ + handleDelete, + handleSelectionChange + }; +} diff --git a/src/views/system/dept/utils/rule.ts b/src/views/system/dept/utils/rule.ts new file mode 100644 index 0000000..b20bf67 --- /dev/null +++ b/src/views/system/dept/utils/rule.ts @@ -0,0 +1,37 @@ +import { reactive } from "vue"; +import type { FormRules } from "element-plus"; +import { isPhone, isEmail } from "@pureadmin/utils"; + +/** 鑷畾涔夎〃鍗曡鍒欐牎楠� */ +export const formRules = reactive(<FormRules>{ + name: [{ required: true, message: "閮ㄩ棬鍚嶇О涓哄繀濉」", trigger: "blur" }], + phone: [ + { + validator: (rule, value, callback) => { + if (value === "") { + callback(); + } else if (!isPhone(value)) { + callback(new Error("璇疯緭鍏ユ纭殑鎵嬫満鍙风爜鏍煎紡")); + } else { + callback(); + } + }, + trigger: "blur" + // trigger: "click" // 濡傛灉鎯冲湪鐐瑰嚮纭畾鎸夐挳鏃惰Е鍙戣繖涓牎楠岋紝trigger 璁剧疆鎴� click 鍗冲彲 + } + ], + email: [ + { + validator: (rule, value, callback) => { + if (value === "") { + callback(); + } else if (!isEmail(value)) { + callback(new Error("璇疯緭鍏ユ纭殑閭鏍煎紡")); + } else { + callback(); + } + }, + trigger: "blur" + } + ] +}); diff --git a/src/views/system/dept/utils/types.ts b/src/views/system/dept/utils/types.ts new file mode 100644 index 0000000..cbf0301 --- /dev/null +++ b/src/views/system/dept/utils/types.ts @@ -0,0 +1,38 @@ +interface FormItemProps { + projectCode: string; // 椤圭洰缂栧彿锛堝繀濉級 + projectName: string; // 椤圭洰鍚嶇О锛堝繀濉級 + hangyepinmu: any | null; // 琛屼笟鍝佺洰锛堝彲閫夛級 + caigoufangshi: any | null; // 閲囪喘鏂瑰紡锛堝彲閫夛級 + caigouyusuan: any | null; // 閲囪喘棰勭畻锛堝彲閫夛級 + dingbiaoguize: any | null; // 瀹氭爣瑙勫垯锛堝彲閫夛級 + baomingfei: any | null; // 鎶ュ悕璐癸紙鍙�夛級 + toubiaobaozhengjin: any | null; // 鎶曟爣淇濊瘉閲戯紙鍙�夛級 + lianhetitoubiao: any | null; // 鑱斿悎浣撴姇鏍囷紙鍙�夛級 + kaibiaofangshi: any | null; // 寮�鏍囨柟寮忥紙鍙�夛級 + shifoufenbao: any | null; // 鏄惁鍒嗗寘锛堝彲閫夛級 + shifoutuisongxuanchuan: any | null; // 鏄惁鎺ㄩ�佸浼狅紙鍙�夛級 + caigourenmingcheng: string | null; // 閲囪喘浜哄悕绉帮紙鍙�夛級 + xingzhengquyu: any | null; // 琛屾斂鍖哄煙锛堝彲閫夛級 + xingzhengquyuName: string | null; // 琛屾斂鍖哄煙鍚嶇О锛堝彲閫夛級 + jigoudaima: string | null; // 鏈烘瀯浠g爜锛堝彲閫夛級 + daimaleixing: any | null; // 浠g爜绫诲瀷锛堝彲閫夛級 + lianxiren: string | null; // 鑱旂郴浜猴紙鍙�夛級 + lianxidianhua: string | null; // 鑱旂郴鐢佃瘽锛堝彲閫夛級 + tongxindizhi: string | null; // 閫氫俊鍦板潃锛堝彲閫夛級 + dianziyoujian: string | null; // 鐢靛瓙閭欢锛堝彲閫夛級 + xiangmujingbanren: string | null; // 椤圭洰缁忓姙浜猴紙鍙�夛級 + zhiwu: string | null; // 鑱屽姟锛堝彲閫夛級 + jingbanrendianhua: string | null; // 缁忓姙浜虹數璇濓紙鍙�夛級 + dailijigoumingcheng: string | null; // 浠g悊鏈烘瀯鍚嶇О锛堝彲閫夛級 + dailiLianxiren: string | null; // 浠g悊鏈烘瀯鑱旂郴浜猴紙鍙�夛級 + dailiLianxidianhua: string | null; // 浠g悊鏈烘瀯鑱旂郴鐢佃瘽锛堝彲閫夛級 + dailiDianziyoujian: string | null; // 浠g悊鏈烘瀯鐢靛瓙閭欢锛堝彲閫夛級 + dailiTongxindizhi: string | null; // 浠g悊鏈烘瀯閫氫俊鍦板潃锛堝彲閫夛級 + dailiXiangmujingli: string | null; // 浠g悊鏈烘瀯椤圭洰缁忕悊锛堝彲閫夛級 + dailijingliLianxidianhua: string | null; // 浠g悊鏈烘瀯椤圭洰缁忕悊鑱旂郴鐢佃瘽锛堝彲閫夛級 +} +interface FormProps { + formInline: FormItemProps; +} + +export type { FormItemProps, FormProps }; diff --git a/src/views/system/hooks.ts b/src/views/system/hooks.ts new file mode 100644 index 0000000..5465d94 --- /dev/null +++ b/src/views/system/hooks.ts @@ -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 + }; +} diff --git a/src/views/system/menu/README.md b/src/views/system/menu/README.md new file mode 100644 index 0000000..a6e8459 --- /dev/null +++ b/src/views/system/menu/README.md @@ -0,0 +1,26 @@ +## 瀛楁鍚箟 + +| 瀛楁 | 璇存槑 | +| :---------------- | :----------------------------------------------------------- | +| `menuType` | 鑿滃崟绫诲瀷锛坄0`浠h〃鑿滃崟銆乣1`浠h〃`iframe`銆乣2`浠h〃澶栭摼銆乣3`浠h〃鎸夐挳锛� | +| `parentId` | | +| `title` | 鑿滃崟鍚嶇О锛堝吋瀹瑰浗闄呭寲銆侀潪鍥介檯鍖栵紝濡傛灉鐢ㄥ浗闄呭寲鐨勫啓娉曞氨蹇呴』鍦ㄦ牴鐩綍鐨刞locales`鏂囦欢澶逛笅瀵瑰簲娣诲姞锛� | +| `name` | 璺敱鍚嶇О锛堝繀椤诲敮涓�骞朵笖鍜屽綋鍓嶈矾鐢盽component`瀛楁瀵瑰簲鐨勯〉闈㈤噷鐢╜defineOptions`鍖呰捣鏉ョ殑`name`淇濇寔涓�鑷达級 | +| `path` | 璺敱璺緞 | +| `component` | 缁勪欢璺緞锛堜紶`component`缁勪欢璺緞锛岄偅涔坄path`鍙互闅忎究鍐欙紝濡傛灉涓嶄紶锛宍component`缁勪欢璺緞浼氳窡`path`淇濇寔涓�鑷达級 | +| `rank` | 鑿滃崟鎺掑簭锛堝钩鍙拌瀹氬彧鏈塦home`璺敱鐨刞rank`鎵嶈兘涓篳0`锛屾墍浠ュ悗绔湪杩斿洖`rank`鐨勬椂鍊欓渶瑕佷粠闈瀈0`寮�濮� [鐐瑰嚮鏌ョ湅鏇村](https://pure-admin.cn/pages/routerMenu/#%E8%8F%9C%E5%8D%95%E6%8E%92%E5%BA%8F-rank)锛� | +| `redirect` | 璺敱閲嶅畾鍚� | +| `icon` | 鑿滃崟鍥炬爣 | +| `extraIcon` | 鍙充晶鍥炬爣 | +| `enterTransition` | 杩涘満鍔ㄧ敾锛堥〉闈㈠姞杞藉姩鐢伙級 | +| `leaveTransition` | 绂诲満鍔ㄧ敾锛堥〉闈㈠姞杞藉姩鐢伙級 | +| `activePath` | 鑿滃崟婵�娲伙紙灏嗘煇涓彍鍗曟縺娲伙紝涓昏鐢ㄤ簬閫氳繃`query`鎴朻params`浼犲弬鐨勮矾鐢憋紝褰撳畠浠�氳繃閰嶇疆`showLink: false`鍚庝笉鍦ㄨ彍鍗曚腑鏄剧ず锛屽氨涓嶄細鏈変换浣曡彍鍗曢珮浜紝鑰岄�氳繃璁剧疆`activePath`鎸囧畾婵�娲昏彍鍗曞嵆鍙幏寰楅珮浜紝`activePath`涓烘寚瀹氭縺娲昏彍鍗曠殑`path`锛� | +| `auths` | 鏉冮檺鏍囪瘑锛堟寜閽骇鍒潈闄愯缃級 | +| `frameSrc` | 閾炬帴鍦板潃锛堥渶瑕佸唴宓岀殑`iframe`閾炬帴鍦板潃锛� | +| `frameLoading` | 鍔犺浇鍔ㄧ敾锛堝唴宓岀殑`iframe`椤甸潰鏄惁寮�鍚娆″姞杞藉姩鐢伙級 | +| `keepAlive` | 缂撳瓨椤甸潰锛堟槸鍚︾紦瀛樿璺敱椤甸潰锛屽紑鍚悗浼氫繚瀛樿椤甸潰鐨勬暣浣撶姸鎬侊紝鍒锋柊鍚庝細娓呯┖鐘舵�侊級 | +| `hiddenTag` | 鏍囩椤碉紙褰撳墠鑿滃崟鍚嶇О鎴栬嚜瀹氫箟淇℃伅绂佹娣诲姞鍒版爣绛鹃〉锛� | +| `fixedTag` | 鍥哄畾鏍囩椤碉紙褰撳墠鑿滃崟鍚嶇О鏄惁鍥哄畾鏄剧ず鍦ㄦ爣绛鹃〉涓斾笉鍙叧闂級 | +| `showLink` | 鑿滃崟锛堟槸鍚︽樉绀鸿鑿滃崟锛� | +| `showParent` | 鐖剁骇鑿滃崟锛堟槸鍚︽樉绀虹埗绾ц彍鍗� [鐐瑰嚮鏌ョ湅鏇村](https://pure-admin.cn/pages/routerMenu/#%E7%AC%AC%E4%B8%80%E7%A7%8D-%E8%AF%A5%E6%A8%A1%E5%BC%8F%E9%92%88%E5%AF%B9%E7%88%B6%E7%BA%A7%E8%8F%9C%E5%8D%95%E4%B8%8B%E5%8F%AA%E6%9C%89%E4%B8%80%E4%B8%AA%E5%AD%90%E8%8F%9C%E5%8D%95%E7%9A%84%E6%83%85%E5%86%B5-%E5%9C%A8%E5%AD%90%E8%8F%9C%E5%8D%95%E7%9A%84-meta-%E5%B1%9E%E6%80%A7%E4%B8%AD%E5%8A%A0%E4%B8%8A-showparent-true-%E5%8D%B3%E5%8F%AF)锛� | + diff --git a/src/views/system/menu/form.vue b/src/views/system/menu/form.vue new file mode 100644 index 0000000..76e8159 --- /dev/null +++ b/src/views/system/menu/form.vue @@ -0,0 +1,342 @@ +<script setup lang="ts"> +import { ref } from "vue"; +import ReCol from "@/components/ReCol"; +import { formRules } from "./utils/rule"; +import { FormProps } from "./utils/types"; +import { transformI18n } from "@/plugins/i18n"; +import { IconSelect } from "@/components/ReIcon"; +import Segmented from "@/components/ReSegmented"; +import ReAnimateSelector from "@/components/ReAnimateSelector"; +import { + menuTypeOptions, + showLinkOptions, + fixedTagOptions, + keepAliveOptions, + hiddenTagOptions, + showParentOptions, + frameLoadingOptions +} from "./utils/enums"; + +const props = withDefaults(defineProps<FormProps>(), { + formInline: () => ({ + menuType: 0, + higherMenuOptions: [], + parentId: 0, + title: "", + name: "", + path: "", + component: "", + rank: 99, + redirect: "", + icon: "", + extraIcon: "", + enterTransition: "", + leaveTransition: "", + activePath: "", + auths: "", + frameSrc: "", + frameLoading: true, + keepAlive: false, + hiddenTag: false, + fixedTag: false, + showLink: true, + showParent: false + }) +}); + +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-row :gutter="30"> + <re-col> + <el-form-item label="鑿滃崟绫诲瀷"> + <Segmented + v-model="newFormInline.menuType" + :options="menuTypeOptions" + /> + </el-form-item> + </re-col> + + <re-col> + <el-form-item label="涓婄骇鑿滃崟"> + <el-cascader + v-model="newFormInline.parentId" + class="w-full" + :options="newFormInline.higherMenuOptions" + :props="{ + value: 'id', + label: 'title', + emitPath: false, + checkStrictly: true + }" + clearable + filterable + placeholder="璇烽�夋嫨涓婄骇鑿滃崟" + > + <template #default="{ node, data }"> + <span>{{ transformI18n(data.title) }}</span> + <span v-if="!node.isLeaf"> ({{ data.children.length }}) </span> + </template> + </el-cascader> + </el-form-item> + </re-col> + + <re-col :value="12" :xs="24" :sm="24"> + <el-form-item label="鑿滃崟鍚嶇О" prop="title"> + <el-input + v-model="newFormInline.title" + clearable + placeholder="璇疯緭鍏ヨ彍鍗曞悕绉�" + /> + </el-form-item> + </re-col> + <re-col v-if="newFormInline.menuType !== 3" :value="12" :xs="24" :sm="24"> + <el-form-item label="璺敱鍚嶇О" prop="name"> + <el-input + v-model="newFormInline.name" + clearable + placeholder="璇疯緭鍏ヨ矾鐢卞悕绉�" + /> + </el-form-item> + </re-col> + + <re-col v-if="newFormInline.menuType !== 3" :value="12" :xs="24" :sm="24"> + <el-form-item label="璺敱璺緞" prop="path"> + <el-input + v-model="newFormInline.path" + clearable + placeholder="璇疯緭鍏ヨ矾鐢辫矾寰�" + /> + </el-form-item> + </re-col> + <re-col + v-show="newFormInline.menuType === 0" + :value="12" + :xs="24" + :sm="24" + > + <el-form-item label="缁勪欢璺緞"> + <el-input + v-model="newFormInline.component" + clearable + placeholder="璇疯緭鍏ョ粍浠惰矾寰�" + /> + </el-form-item> + </re-col> + + <re-col :value="12" :xs="24" :sm="24"> + <el-form-item label="鑿滃崟鎺掑簭"> + <el-input-number + v-model="newFormInline.rank" + class="w-full!" + :min="1" + :max="9999" + controls-position="right" + /> + </el-form-item> + </re-col> + <re-col + v-show="newFormInline.menuType === 0" + :value="12" + :xs="24" + :sm="24" + > + <el-form-item label="璺敱閲嶅畾鍚�"> + <el-input + v-model="newFormInline.redirect" + clearable + placeholder="璇疯緭鍏ラ粯璁よ烦杞湴鍧�" + /> + </el-form-item> + </re-col> + + <re-col + v-show="newFormInline.menuType !== 3" + :value="12" + :xs="24" + :sm="24" + > + <el-form-item label="鑿滃崟鍥炬爣"> + <IconSelect v-model="newFormInline.icon" class="w-full" /> + </el-form-item> + </re-col> + <re-col + v-show="newFormInline.menuType !== 3" + :value="12" + :xs="24" + :sm="24" + > + <el-form-item label="鍙充晶鍥炬爣"> + <el-input + v-model="newFormInline.extraIcon" + clearable + placeholder="鑿滃崟鍚嶇О鍙充晶鐨勯澶栧浘鏍�" + /> + </el-form-item> + </re-col> + + <re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24"> + <el-form-item label="杩涘満鍔ㄧ敾"> + <ReAnimateSelector + v-model="newFormInline.enterTransition" + placeholder="璇烽�夋嫨椤甸潰杩涘満鍔犺浇鍔ㄧ敾" + /> + </el-form-item> + </re-col> + <re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24"> + <el-form-item label="绂诲満鍔ㄧ敾"> + <ReAnimateSelector + v-model="newFormInline.leaveTransition" + placeholder="璇烽�夋嫨椤甸潰绂诲満鍔犺浇鍔ㄧ敾" + /> + </el-form-item> + </re-col> + + <re-col + v-show="newFormInline.menuType === 0" + :value="12" + :xs="24" + :sm="24" + > + <el-form-item label="鑿滃崟婵�娲�"> + <el-input + v-model="newFormInline.activePath" + clearable + placeholder="璇疯緭鍏ラ渶瑕佹縺娲荤殑鑿滃崟" + /> + </el-form-item> + </re-col> + <re-col v-if="newFormInline.menuType === 3" :value="12" :xs="24" :sm="24"> + <!-- 鎸夐挳绾у埆鏉冮檺璁剧疆 --> + <el-form-item label="鏉冮檺鏍囪瘑" prop="auths"> + <el-input + v-model="newFormInline.auths" + clearable + placeholder="璇疯緭鍏ユ潈闄愭爣璇�" + /> + </el-form-item> + </re-col> + + <re-col + v-show="newFormInline.menuType === 1" + :value="12" + :xs="24" + :sm="24" + > + <!-- iframe --> + <el-form-item label="閾炬帴鍦板潃"> + <el-input + v-model="newFormInline.frameSrc" + clearable + placeholder="璇疯緭鍏� iframe 閾炬帴鍦板潃" + /> + </el-form-item> + </re-col> + <re-col v-if="newFormInline.menuType === 1" :value="12" :xs="24" :sm="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 + v-show="newFormInline.menuType !== 3" + :value="12" + :xs="24" + :sm="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 + v-show="newFormInline.menuType !== 3" + :value="12" + :xs="24" + :sm="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" :value="12" :xs="24" :sm="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" :value="12" :xs="24" :sm="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" :value="12" :xs="24" :sm="24"> + <el-form-item label="鍥哄畾鏍囩椤�"> + <Segmented + :modelValue="newFormInline.fixedTag ? 0 : 1" + :options="fixedTagOptions" + @change=" + ({ option: { value } }) => { + newFormInline.fixedTag = value; + } + " + /> + </el-form-item> + </re-col> + </el-row> + </el-form> +</template> diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue new file mode 100644 index 0000000..7c60010 --- /dev/null +++ b/src/views/system/menu/index.vue @@ -0,0 +1,163 @@ +<script setup lang="ts"> +import { ref } from "vue"; +import { useMenu } from "./utils/hook"; +import { transformI18n } from "@/plugins/i18n"; +import { PureTableBar } from "@/components/RePureTableBar"; +import { useRenderIcon } from "@/components/ReIcon/src/hooks"; + +import Delete from "~icons/ep/delete"; +import EditPen from "~icons/ep/edit-pen"; +import Refresh from "~icons/ep/refresh"; +import AddFill from "~icons/ri/add-circle-line"; + +defineOptions({ + name: "SystemMenu" +}); + +const formRef = ref(); +const tableRef = ref(); +const { + form, + loading, + columns, + dataList, + onSearch, + resetForm, + openDialog, + handleDelete, + handleSelectionChange +} = useMenu(); + +function onFullscreen() { + // 閲嶇疆琛ㄦ牸楂樺害 + tableRef.value.setAdaptive(); +} +</script> + +<template> + <div class="main"> + <el-form + ref="formRef" + :inline="true" + :model="form" + class="search-form bg-bg_color w-full pl-8 pt-[12px] overflow-auto" + > + <el-form-item label="鑿滃崟鍚嶇О锛�" prop="title"> + <el-input + v-model="form.title" + placeholder="璇疯緭鍏ヨ彍鍗曞悕绉�" + clearable + class="w-[180px]!" + /> + </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> + + <PureTableBar + title="鑿滃崟绠$悊锛堜粎婕旂ず锛屾搷浣滃悗涓嶇敓鏁堬級" + :columns="columns" + :isExpandAll="false" + :tableRef="tableRef?.getTableRef()" + @refresh="onSearch" + @fullscreen="onFullscreen" + > + <template #buttons> + <el-button + type="primary" + :icon="useRenderIcon(AddFill)" + @click="openDialog()" + > + 鏂板鑿滃崟 + </el-button> + </template> + <template v-slot="{ size, dynamicColumns }"> + <pure-table + ref="tableRef" + adaptive + :adaptiveConfig="{ offsetBottom: 45 }" + align-whole="center" + row-key="id" + showOverflowTooltip + table-layout="auto" + :loading="loading" + :size="size" + :data="dataList" + :columns="dynamicColumns" + :header-cell-style="{ + background: 'var(--el-fill-color-light)', + color: 'var(--el-text-color-primary)' + }" + @selection-change="handleSelectionChange" + > + <template #operation="{ row }"> + <el-button + class="reset-margin" + link + type="primary" + :size="size" + :icon="useRenderIcon(EditPen)" + @click="openDialog('淇敼', row)" + > + 淇敼 + </el-button> + <el-button + v-show="row.menuType !== 3" + class="reset-margin" + link + type="primary" + :size="size" + :icon="useRenderIcon(AddFill)" + @click="openDialog('鏂板', { parentId: row.id } as any)" + > + 鏂板 + </el-button> + <el-popconfirm + :title="`鏄惁纭鍒犻櫎鑿滃崟鍚嶇О涓�${transformI18n(row.title)}鐨勮繖鏉℃暟鎹�${row?.children?.length > 0 ? '銆傛敞鎰忎笅绾ц彍鍗曚篃浼氫竴骞跺垹闄わ紝璇疯皑鎱庢搷浣�' : ''}`" + @confirm="handleDelete(row)" + > + <template #reference> + <el-button + class="reset-margin" + link + type="primary" + :size="size" + :icon="useRenderIcon(Delete)" + > + 鍒犻櫎 + </el-button> + </template> + </el-popconfirm> + </template> + </pure-table> + </template> + </PureTableBar> + </div> +</template> + +<style lang="scss" scoped> +:deep(.el-table__inner-wrapper::before) { + height: 0; +} + +.main-content { + margin: 24px 24px 0 !important; +} + +.search-form { + :deep(.el-form-item) { + margin-bottom: 12px; + } +} +</style> diff --git a/src/views/system/menu/utils/enums.ts b/src/views/system/menu/utils/enums.ts new file mode 100644 index 0000000..4466b61 --- /dev/null +++ b/src/views/system/menu/utils/enums.ts @@ -0,0 +1,108 @@ +import type { OptionsType } from "@/components/ReSegmented"; + +const menuTypeOptions: Array<OptionsType> = [ + { + label: "鑿滃崟", + value: 0 + }, + { + label: "iframe", + value: 1 + }, + { + label: "澶栭摼", + value: 2 + }, + { + label: "鎸夐挳", + value: 3 + } +]; + +const showLinkOptions: Array<OptionsType> = [ + { + label: "鏄剧ず", + tip: "浼氬湪鑿滃崟涓樉绀�", + value: true + }, + { + label: "闅愯棌", + tip: "涓嶄細鍦ㄨ彍鍗曚腑鏄剧ず", + value: false + } +]; + +const fixedTagOptions: Array<OptionsType> = [ + { + label: "鍥哄畾", + tip: "褰撳墠鑿滃崟鍚嶇О鍥哄畾鏄剧ず鍦ㄦ爣绛鹃〉涓斾笉鍙叧闂�", + value: true + }, + { + label: "涓嶅浐瀹�", + tip: "褰撳墠鑿滃崟鍚嶇О涓嶅浐瀹氭樉绀哄湪鏍囩椤典笖鍙叧闂�", + value: false + } +]; + +const keepAliveOptions: Array<OptionsType> = [ + { + label: "缂撳瓨", + tip: "浼氫繚瀛樿椤甸潰鐨勬暣浣撶姸鎬侊紝鍒锋柊鍚庝細娓呯┖鐘舵��", + value: true + }, + { + label: "涓嶇紦瀛�", + tip: "涓嶄細淇濆瓨璇ラ〉闈㈢殑鏁翠綋鐘舵��", + value: false + } +]; + +const hiddenTagOptions: Array<OptionsType> = [ + { + label: "鍏佽", + tip: "褰撳墠鑿滃崟鍚嶇О鎴栬嚜瀹氫箟淇℃伅鍏佽娣诲姞鍒版爣绛鹃〉", + value: false + }, + { + label: "绂佹", + tip: "褰撳墠鑿滃崟鍚嶇О鎴栬嚜瀹氫箟淇℃伅绂佹娣诲姞鍒版爣绛鹃〉", + value: true + } +]; + +const showParentOptions: Array<OptionsType> = [ + { + label: "鏄剧ず", + tip: "浼氭樉绀虹埗绾ц彍鍗�", + value: true + }, + { + label: "闅愯棌", + tip: "涓嶄細鏄剧ず鐖剁骇鑿滃崟", + value: false + } +]; + +const frameLoadingOptions: Array<OptionsType> = [ + { + label: "寮�鍚�", + tip: "鏈夐娆″姞杞藉姩鐢�", + value: true + }, + { + label: "鍏抽棴", + tip: "鏃犻娆″姞杞藉姩鐢�", + value: false + } +]; + +export { + menuTypeOptions, + showLinkOptions, + fixedTagOptions, + keepAliveOptions, + hiddenTagOptions, + showParentOptions, + frameLoadingOptions +}; diff --git a/src/views/system/menu/utils/hook.tsx b/src/views/system/menu/utils/hook.tsx new file mode 100644 index 0000000..822f3da --- /dev/null +++ b/src/views/system/menu/utils/hook.tsx @@ -0,0 +1,225 @@ +import editForm from "../form.vue"; +import { handleTree } from "@/utils/tree"; +import { message } from "@/utils/message"; +import { getMenuList } from "@/api/system"; +import { transformI18n } from "@/plugins/i18n"; +import { addDialog } from "@/components/ReDialog"; +import { reactive, ref, onMounted, h } from "vue"; +import type { FormItemProps } from "../utils/types"; +import { useRenderIcon } from "@/components/ReIcon/src/hooks"; +import { cloneDeep, isAllEmpty, deviceDetection } from "@pureadmin/utils"; + +export function useMenu() { + const form = reactive({ + title: "" + }); + + const formRef = ref(); + const dataList = ref([]); + const loading = ref(true); + + const getMenuType = (type, text = false) => { + switch (type) { + case 0: + return text ? "鑿滃崟" : "primary"; + case 1: + return text ? "iframe" : "warning"; + case 2: + return text ? "澶栭摼" : "danger"; + case 3: + return text ? "鎸夐挳" : "info"; + } + }; + + const columns: TableColumnList = [ + { + label: "鑿滃崟鍚嶇О", + prop: "title", + align: "left", + cellRenderer: ({ row }) => ( + <> + <span class="inline-block mr-1"> + {h(useRenderIcon(row.icon), { + style: { paddingTop: "1px" } + })} + </span> + <span>{transformI18n(row.title)}</span> + </> + ) + }, + { + label: "鑿滃崟绫诲瀷", + prop: "menuType", + width: 100, + cellRenderer: ({ row, props }) => ( + <el-tag + size={props.size} + type={getMenuType(row.menuType)} + effect="plain" + > + {getMenuType(row.menuType, true)} + </el-tag> + ) + }, + { + label: "璺敱璺緞", + prop: "path" + }, + { + label: "缁勪欢璺緞", + prop: "component", + formatter: ({ path, component }) => + isAllEmpty(component) ? path : component + }, + { + label: "鏉冮檺鏍囪瘑", + prop: "auths" + }, + { + label: "鎺掑簭", + prop: "rank", + width: 100 + }, + { + label: "闅愯棌", + prop: "showLink", + formatter: ({ showLink }) => (showLink ? "鍚�" : "鏄�"), + width: 100 + }, + { + label: "鎿嶄綔", + fixed: "right", + width: 210, + slot: "operation" + } + ]; + + function handleSelectionChange(val) { + console.log("handleSelectionChange", val); + } + + function resetForm(formEl) { + if (!formEl) return; + formEl.resetFields(); + onSearch(); + } + + async function onSearch() { + loading.value = true; + const { data } = await getMenuList(); // 杩欓噷鏄繑鍥炰竴缁存暟缁勭粨鏋勶紝鍓嶇鑷澶勭悊鎴愭爲缁撴瀯锛岃繑鍥炴牸寮忚姹傦細鍞竴id鍔犵埗鑺傜偣parentId锛宲arentId鍙栫埗鑺傜偣id + let newData = data; + if (!isAllEmpty(form.title)) { + // 鍓嶇鎼滅储鑿滃崟鍚嶇О + newData = newData.filter(item => + transformI18n(item.title).includes(form.title) + ); + } + dataList.value = handleTree(newData); // 澶勭悊鎴愭爲缁撴瀯 + setTimeout(() => { + loading.value = false; + }, 500); + } + + function formatHigherMenuOptions(treeList) { + if (!treeList || !treeList.length) return; + const newTreeList = []; + for (let i = 0; i < treeList.length; i++) { + treeList[i].title = transformI18n(treeList[i].title); + formatHigherMenuOptions(treeList[i].children); + newTreeList.push(treeList[i]); + } + return newTreeList; + } + + function openDialog(title = "鏂板", row?: FormItemProps) { + addDialog({ + 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 ?? "", + extraIcon: row?.extraIcon ?? "", + enterTransition: row?.enterTransition ?? "", + leaveTransition: row?.leaveTransition ?? "", + activePath: row?.activePath ?? "", + auths: row?.auths ?? "", + 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 + } + }, + width: "45%", + 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}浜嗚彍鍗曞悕绉颁负${transformI18n(curData.title)}鐨勮繖鏉℃暟鎹甡, + { + type: "success" + } + ); + done(); // 鍏抽棴寮规 + onSearch(); // 鍒锋柊琛ㄦ牸鏁版嵁 + } + FormRef.validate(valid => { + if (valid) { + console.log("curData", curData); + // 琛ㄥ崟瑙勫垯鏍¢獙閫氳繃 + if (title === "鏂板") { + // 瀹為檯寮�鍙戝厛璋冪敤鏂板鎺ュ彛锛屽啀杩涜涓嬮潰鎿嶄綔 + chores(); + } else { + // 瀹為檯寮�鍙戝厛璋冪敤淇敼鎺ュ彛锛屽啀杩涜涓嬮潰鎿嶄綔 + chores(); + } + } + }); + } + }); + } + + function handleDelete(row) { + message(`鎮ㄥ垹闄や簡鑿滃崟鍚嶇О涓�${transformI18n(row.title)}鐨勮繖鏉℃暟鎹甡, { + type: "success" + }); + onSearch(); + } + + onMounted(() => { + onSearch(); + }); + + return { + form, + loading, + columns, + dataList, + /** 鎼滅储 */ + onSearch, + /** 閲嶇疆 */ + resetForm, + /** 鏂板銆佷慨鏀硅彍鍗� */ + openDialog, + /** 鍒犻櫎鑿滃崟 */ + handleDelete, + handleSelectionChange + }; +} diff --git a/src/views/system/menu/utils/rule.ts b/src/views/system/menu/utils/rule.ts new file mode 100644 index 0000000..90b3548 --- /dev/null +++ b/src/views/system/menu/utils/rule.ts @@ -0,0 +1,10 @@ +import { reactive } from "vue"; +import type { FormRules } from "element-plus"; + +/** 鑷畾涔夎〃鍗曡鍒欐牎楠� */ +export const formRules = reactive(<FormRules>{ + title: [{ required: true, message: "鑿滃崟鍚嶇О涓哄繀濉」", trigger: "blur" }], + name: [{ required: true, message: "璺敱鍚嶇О涓哄繀濉」", trigger: "blur" }], + path: [{ required: true, message: "璺敱璺緞涓哄繀濉」", trigger: "blur" }], + auths: [{ required: true, message: "鏉冮檺鏍囪瘑涓哄繀濉」", trigger: "blur" }] +}); diff --git a/src/views/system/menu/utils/types.ts b/src/views/system/menu/utils/types.ts new file mode 100644 index 0000000..f07d1f7 --- /dev/null +++ b/src/views/system/menu/utils/types.ts @@ -0,0 +1,30 @@ +interface FormItemProps { + /** 鑿滃崟绫诲瀷锛�0浠h〃鑿滃崟銆�1浠h〃iframe銆�2浠h〃澶栭摼銆�3浠h〃鎸夐挳锛�*/ + menuType: number; + higherMenuOptions: Record<string, unknown>[]; + parentId: number; + title: string; + name: string; + path: string; + component: string; + rank: number; + redirect: string; + icon: string; + extraIcon: string; + enterTransition: string; + leaveTransition: string; + activePath: string; + auths: string; + frameSrc: string; + frameLoading: boolean; + keepAlive: boolean; + hiddenTag: boolean; + fixedTag: boolean; + showLink: boolean; + showParent: boolean; +} +interface FormProps { + formInline: FormItemProps; +} + +export type { FormItemProps, FormProps }; diff --git a/src/views/system/role/form.vue b/src/views/system/role/form.vue new file mode 100644 index 0000000..65d4ef0 --- /dev/null +++ b/src/views/system/role/form.vue @@ -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> diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue new file mode 100644 index 0000000..ed7c3dd --- /dev/null +++ b/src/views/system/role/index.vue @@ -0,0 +1,344 @@ +<script setup lang="ts"> +import { useRole } from "./utils/hook"; +import { ref, computed, nextTick, onMounted } from "vue"; +import { PureTableBar } from "@/components/RePureTableBar"; +import { useRenderIcon } from "@/components/ReIcon/src/hooks"; +import { + delay, + subBefore, + deviceDetection, + useResizeObserver +} from "@pureadmin/utils"; + +// import Database from "~icons/ri/database-2-line"; +// import More from "~icons/ep/more-filled"; +import Delete from "~icons/ep/delete"; +import EditPen from "~icons/ep/edit-pen"; +import Refresh from "~icons/ep/refresh"; +import Menu from "~icons/ep/menu"; +import AddFill from "~icons/ri/add-circle-line"; +import Close from "~icons/ep/close"; +import Check from "~icons/ep/check"; + +defineOptions({ + name: "SystemRole" +}); + +const iconClass = computed(() => { + return [ + "w-[22px]", + "h-[22px]", + "flex", + "justify-center", + "items-center", + "outline-hidden", + "rounded-[4px]", + "cursor-pointer", + "transition-colors", + "hover:bg-[#0000000f]", + "dark:hover:bg-[#ffffff1f]", + "dark:hover:text-[#ffffffd9]" + ]; +}); + +const treeRef = ref(); +const formRef = ref(); +const tableRef = ref(); +const contentRef = ref(); +const treeHeight = ref(); + +const { + form, + isShow, + curRow, + loading, + columns, + rowStyle, + dataList, + treeData, + treeProps, + isLinkage, + pagination, + isExpandAll, + isSelectAll, + treeSearchValue, + // buttonClass, + onSearch, + resetForm, + openDialog, + handleMenu, + handleSave, + handleDelete, + filterMethod, + transformI18n, + onQueryChanged, + // handleDatabase, + handleSizeChange, + handleCurrentChange, + handleSelectionChange +} = useRole(treeRef); + +onMounted(() => { + useResizeObserver(contentRef, async () => { + await nextTick(); + delay(60).then(() => { + treeHeight.value = parseFloat( + subBefore(tableRef.value.getTableDoms().tableWrapper.style.height, "px") + ); + }); + }); +}); +</script> + +<template> + <div class="main"> + <el-form + ref="formRef" + :inline="true" + :model="form" + class="search-form bg-bg_color w-full pl-8 pt-[12px] overflow-auto" + > + <el-form-item label="瑙掕壊鍚嶇О锛�" prop="name"> + <el-input + v-model="form.name" + placeholder="璇疯緭鍏ヨ鑹插悕绉�" + clearable + class="w-[180px]!" + /> + </el-form-item> + <el-form-item label="瑙掕壊鏍囪瘑锛�" prop="code"> + <el-input + v-model="form.code" + placeholder="璇疯緭鍏ヨ鑹叉爣璇�" + clearable + class="w-[180px]!" + /> + </el-form-item> + <el-form-item label="鐘舵�侊細" prop="status"> + <el-select + v-model="form.status" + placeholder="璇烽�夋嫨鐘舵��" + clearable + class="w-[180px]!" + > + <el-option label="宸插惎鐢�" value="1" /> + <el-option label="宸插仠鐢�" value="0" /> + </el-select> + </el-form-item> + <el-form-item> + <el-button + type="primary" + :icon="useRenderIcon('ri/search-line')" + :loading="loading" + @click="onSearch" + > + 鎼滅储 + </el-button> + <el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> + 閲嶇疆 + </el-button> + </el-form-item> + </el-form> + + <div + ref="contentRef" + :class="['flex', deviceDetection() ? 'flex-wrap' : '']" + > + <PureTableBar + :class="[isShow && !deviceDetection() ? 'w-[60vw]!' : 'w-full']" + style="transition: width 220ms cubic-bezier(0.4, 0, 0.2, 1)" + title="瑙掕壊绠$悊锛堜粎婕旂ず锛屾搷浣滃悗涓嶇敓鏁堬級" + :columns="columns" + @refresh="onSearch" + > + <template #buttons> + <el-button + type="primary" + :icon="useRenderIcon(AddFill)" + @click="openDialog()" + > + 鏂板瑙掕壊 + </el-button> + </template> + <template v-slot="{ size, dynamicColumns }"> + <pure-table + ref="tableRef" + align-whole="center" + showOverflowTooltip + table-layout="auto" + :loading="loading" + :size="size" + adaptive + :row-style="rowStyle" + :adaptiveConfig="{ offsetBottom: 108 }" + :data="dataList" + :columns="dynamicColumns" + :pagination="{ ...pagination, size }" + :header-cell-style="{ + background: 'var(--el-fill-color-light)', + color: 'var(--el-text-color-primary)' + }" + @selection-change="handleSelectionChange" + @page-size-change="handleSizeChange" + @page-current-change="handleCurrentChange" + > + <template #operation="{ row }"> + <el-button + class="reset-margin" + link + type="primary" + :size="size" + :icon="useRenderIcon(EditPen)" + @click="openDialog('淇敼', row)" + > + 淇敼 + </el-button> + <el-popconfirm + :title="`鏄惁纭鍒犻櫎瑙掕壊鍚嶇О涓�${row.name}鐨勮繖鏉℃暟鎹甡" + @confirm="handleDelete(row)" + > + <template #reference> + <el-button + class="reset-margin" + link + type="primary" + :size="size" + :icon="useRenderIcon(Delete)" + > + 鍒犻櫎 + </el-button> + </template> + </el-popconfirm> + <el-button + class="reset-margin" + link + type="primary" + :size="size" + :icon="useRenderIcon(Menu)" + @click="handleMenu(row)" + > + 鏉冮檺 + </el-button> + <!-- <el-dropdown> + <el-button + class="ml-3 mt-[2px]" + link + type="primary" + :size="size" + :icon="useRenderIcon(More)" + /> + <template #dropdown> + <el-dropdown-menu> + <el-dropdown-item> + <el-button + :class="buttonClass" + link + type="primary" + :size="size" + :icon="useRenderIcon(Menu)" + @click="handleMenu" + > + 鑿滃崟鏉冮檺 + </el-button> + </el-dropdown-item> + <el-dropdown-item> + <el-button + :class="buttonClass" + link + type="primary" + :size="size" + :icon="useRenderIcon(Database)" + @click="handleDatabase" + > + 鏁版嵁鏉冮檺 + </el-button> + </el-dropdown-item> + </el-dropdown-menu> + </template> + </el-dropdown> --> + </template> + </pure-table> + </template> + </PureTableBar> + + <div + v-if="isShow" + class="min-w-[calc(100vw-60vw-268px)]! w-full mt-2 px-2 pb-2 bg-bg_color ml-2 overflow-auto" + > + <div class="flex justify-between w-full px-3 pt-5 pb-4"> + <div class="flex"> + <span :class="iconClass"> + <IconifyIconOffline + v-tippy="{ + content: '鍏抽棴' + }" + class="dark:text-white" + width="18px" + height="18px" + :icon="Close" + @click="handleMenu" + /> + </span> + <span :class="[iconClass, 'ml-2']"> + <IconifyIconOffline + v-tippy="{ + content: '淇濆瓨鑿滃崟鏉冮檺' + }" + class="dark:text-white" + width="18px" + height="18px" + :icon="Check" + @click="handleSave" + /> + </span> + </div> + <p class="font-bold truncate"> + 鑿滃崟鏉冮檺 + {{ `${curRow?.name ? `锛�${curRow.name}锛塦 : ""}` }} + </p> + </div> + <el-input + v-model="treeSearchValue" + placeholder="璇疯緭鍏ヨ彍鍗曡繘琛屾悳绱�" + class="mb-1" + clearable + @input="onQueryChanged" + /> + <div class="flex flex-wrap"> + <el-checkbox v-model="isExpandAll" label="灞曞紑/鎶樺彔" /> + <el-checkbox v-model="isSelectAll" label="鍏ㄩ��/鍏ㄤ笉閫�" /> + <el-checkbox v-model="isLinkage" label="鐖跺瓙鑱斿姩" /> + </div> + <el-tree-v2 + ref="treeRef" + show-checkbox + :data="treeData" + :props="treeProps" + :height="treeHeight" + :check-strictly="!isLinkage" + :filter-method="filterMethod" + > + <template #default="{ node }"> + <span>{{ transformI18n(node.label) }}</span> + </template> + </el-tree-v2> + </div> + </div> + </div> +</template> + +<style lang="scss" scoped> +:deep(.el-dropdown-menu__item i) { + margin: 0; +} + +.main-content { + margin: 24px 24px 0 !important; +} + +.search-form { + :deep(.el-form-item) { + margin-bottom: 12px; + } +} +</style> diff --git a/src/views/system/role/utils/hook.tsx b/src/views/system/role/utils/hook.tsx new file mode 100644 index 0000000..a57c4d1 --- /dev/null +++ b/src/views/system/role/utils/hook.tsx @@ -0,0 +1,318 @@ +import dayjs from "dayjs"; +import editForm from "../form.vue"; +import { handleTree } from "@/utils/tree"; +import { message } from "@/utils/message"; +import { ElMessageBox } from "element-plus"; +import { usePublicHooks } from "../../hooks"; +import { transformI18n } from "@/plugins/i18n"; +import { addDialog } from "@/components/ReDialog"; +import type { FormItemProps } from "../utils/types"; +import type { PaginationProps } from "@pureadmin/table"; +import { getKeyList, deviceDetection } from "@pureadmin/utils"; +import { getRoleList, getRoleMenu, getRoleMenuIds } from "@/api/system"; +import { type Ref, reactive, ref, onMounted, h, toRaw, watch } from "vue"; + +export function useRole(treeRef: Ref) { + const form = reactive({ + name: "", + code: "", + status: "" + }); + const curRow = ref(); + const formRef = ref(); + const dataList = ref([]); + const treeIds = ref([]); + const treeData = ref([]); + const isShow = ref(false); + const loading = ref(true); + const isLinkage = ref(false); + const treeSearchValue = ref(); + const switchLoadMap = ref({}); + const isExpandAll = ref(false); + const isSelectAll = ref(false); + const { switchStyle } = usePublicHooks(); + const treeProps = { + value: "id", + label: "title", + children: "children" + }; + const pagination = reactive<PaginationProps>({ + total: 0, + pageSize: 10, + currentPage: 1, + background: true + }); + const columns: TableColumnList = [ + { + label: "瑙掕壊缂栧彿", + prop: "id" + }, + { + label: "瑙掕壊鍚嶇О", + prop: "name" + }, + { + label: "瑙掕壊鏍囪瘑", + prop: "code" + }, + { + label: "鐘舵��", + cellRenderer: scope => ( + <el-switch + size={scope.props.size === "small" ? "small" : "default"} + loading={switchLoadMap.value[scope.index]?.loading} + v-model={scope.row.status} + active-value={1} + inactive-value={0} + active-text="宸插惎鐢�" + inactive-text="宸插仠鐢�" + inline-prompt + style={switchStyle.value} + onChange={() => onChange(scope as any)} + /> + ), + minWidth: 90 + }, + { + label: "澶囨敞", + prop: "remark", + minWidth: 160 + }, + { + label: "鍒涘缓鏃堕棿", + prop: "createTime", + minWidth: 160, + formatter: ({ createTime }) => + dayjs(createTime).format("YYYY-MM-DD HH:mm:ss") + }, + { + label: "鎿嶄綔", + fixed: "right", + width: 210, + slot: "operation" + } + ]; + // const buttonClass = computed(() => { + // return [ + // "h-[20px]!", + // "reset-margin", + // "text-gray-500!", + // "dark:text-white!", + // "dark:hover:text-primary!" + // ]; + // }); + + function onChange({ row, index }) { + ElMessageBox.confirm( + `纭瑕�<strong>${ + row.status === 0 ? "鍋滅敤" : "鍚敤" + }</strong><strong style='color:var(--el-color-primary)'>${ + row.name + }</strong>鍚�?`, + "绯荤粺鎻愮ず", + { + confirmButtonText: "纭畾", + cancelButtonText: "鍙栨秷", + type: "warning", + dangerouslyUseHTMLString: true, + draggable: true + } + ) + .then(() => { + switchLoadMap.value[index] = Object.assign( + {}, + switchLoadMap.value[index], + { + loading: true + } + ); + setTimeout(() => { + switchLoadMap.value[index] = Object.assign( + {}, + switchLoadMap.value[index], + { + loading: false + } + ); + message(`宸�${row.status === 0 ? "鍋滅敤" : "鍚敤"}${row.name}`, { + type: "success" + }); + }, 300); + }) + .catch(() => { + row.status === 0 ? (row.status = 1) : (row.status = 0); + }); + } + + function handleDelete(row) { + message(`鎮ㄥ垹闄や簡瑙掕壊鍚嶇О涓�${row.name}鐨勮繖鏉℃暟鎹甡, { type: "success" }); + onSearch(); + } + + function handleSizeChange(val: number) { + console.log(`${val} items per page`); + } + + function handleCurrentChange(val: number) { + console.log(`current page: ${val}`); + } + + function handleSelectionChange(val) { + console.log("handleSelectionChange", val); + } + + async function onSearch() { + loading.value = true; + const { data } = await getRoleList(toRaw(form)); + dataList.value = data.list; + pagination.total = data.total; + pagination.pageSize = data.pageSize; + pagination.currentPage = data.currentPage; + + setTimeout(() => { + loading.value = false; + }, 500); + } + + const resetForm = formEl => { + if (!formEl) return; + formEl.resetFields(); + onSearch(); + }; + + function openDialog(title = "鏂板", row?: FormItemProps) { + addDialog({ + title: `${title}瑙掕壊`, + props: { + formInline: { + name: row?.name ?? "", + code: row?.code ?? "", + remark: row?.remark ?? "" + } + }, + width: "40%", + draggable: true, + fullscreen: deviceDetection(), + fullscreenIcon: true, + closeOnClickModal: false, + contentRenderer: () => h(editForm, { ref: formRef, formInline: null }), + beforeSure: (done, { options }) => { + const FormRef = formRef.value.getRef(); + const curData = options.props.formInline as FormItemProps; + function chores() { + message(`鎮�${title}浜嗚鑹插悕绉颁负${curData.name}鐨勮繖鏉℃暟鎹甡, { + type: "success" + }); + done(); // 鍏抽棴寮规 + onSearch(); // 鍒锋柊琛ㄦ牸鏁版嵁 + } + FormRef.validate(valid => { + if (valid) { + console.log("curData", curData); + // 琛ㄥ崟瑙勫垯鏍¢獙閫氳繃 + if (title === "鏂板") { + // 瀹為檯寮�鍙戝厛璋冪敤鏂板鎺ュ彛锛屽啀杩涜涓嬮潰鎿嶄綔 + chores(); + } else { + // 瀹為檯寮�鍙戝厛璋冪敤淇敼鎺ュ彛锛屽啀杩涜涓嬮潰鎿嶄綔 + chores(); + } + } + }); + } + }); + } + + /** 鑿滃崟鏉冮檺 */ + async function handleMenu(row?: any) { + const { id } = row; + if (id) { + curRow.value = row; + isShow.value = true; + const { data } = await getRoleMenuIds({ id }); + treeRef.value.setCheckedKeys(data); + } else { + curRow.value = null; + isShow.value = false; + } + } + + /** 楂樹寒褰撳墠鏉冮檺閫変腑琛� */ + function rowStyle({ row: { id } }) { + return { + cursor: "pointer", + background: id === curRow.value?.id ? "var(--el-fill-color-light)" : "" + }; + } + + /** 鑿滃崟鏉冮檺-淇濆瓨 */ + function handleSave() { + const { id, name } = curRow.value; + // 鏍规嵁鐢ㄦ埛 id 璋冪敤瀹為檯椤圭洰涓彍鍗曟潈闄愪慨鏀规帴鍙� + console.log(id, treeRef.value.getCheckedKeys()); + message(`瑙掕壊鍚嶇О涓�${name}鐨勮彍鍗曟潈闄愪慨鏀规垚鍔焋, { + type: "success" + }); + } + + /** 鏁版嵁鏉冮檺 鍙嚜琛屽紑鍙� */ + // function handleDatabase() {} + + const onQueryChanged = (query: string) => { + treeRef.value!.filter(query); + }; + + const filterMethod = (query: string, node) => { + return transformI18n(node.title)!.includes(query); + }; + + onMounted(async () => { + onSearch(); + const { data } = await getRoleMenu(); + treeIds.value = getKeyList(data, "id"); + treeData.value = handleTree(data); + }); + + watch(isExpandAll, val => { + val + ? treeRef.value.setExpandedKeys(treeIds.value) + : treeRef.value.setExpandedKeys([]); + }); + + watch(isSelectAll, val => { + val + ? treeRef.value.setCheckedKeys(treeIds.value) + : treeRef.value.setCheckedKeys([]); + }); + + return { + form, + isShow, + curRow, + loading, + columns, + rowStyle, + dataList, + treeData, + treeProps, + isLinkage, + pagination, + isExpandAll, + isSelectAll, + treeSearchValue, + // buttonClass, + onSearch, + resetForm, + openDialog, + handleMenu, + handleSave, + handleDelete, + filterMethod, + transformI18n, + onQueryChanged, + // handleDatabase, + handleSizeChange, + handleCurrentChange, + handleSelectionChange + }; +} diff --git a/src/views/system/role/utils/rule.ts b/src/views/system/role/utils/rule.ts new file mode 100644 index 0000000..ea1dd19 --- /dev/null +++ b/src/views/system/role/utils/rule.ts @@ -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" }] +}); diff --git a/src/views/system/role/utils/types.ts b/src/views/system/role/utils/types.ts new file mode 100644 index 0000000..a17e900 --- /dev/null +++ b/src/views/system/role/utils/types.ts @@ -0,0 +1,15 @@ +// 铏界劧瀛楁寰堝皯 浣嗘槸鎶界鍑烘潵 鍚庣画鏈夋墿灞曞瓧娈甸渶姹傚氨寰堟柟渚夸簡 + +interface FormItemProps { + /** 瑙掕壊鍚嶇О */ + name: string; + /** 瑙掕壊缂栧彿 */ + code: string; + /** 澶囨敞 */ + remark: string; +} +interface FormProps { + formInline: FormItemProps; +} + +export type { FormItemProps, FormProps }; diff --git a/src/views/system/user/form/index.vue b/src/views/system/user/form/index.vue new file mode 100644 index 0000000..f6834da --- /dev/null +++ b/src/views/system/user/form/index.vue @@ -0,0 +1,176 @@ +<script setup lang="ts"> +import { ref } from "vue"; +import ReCol from "@/components/ReCol"; +import { formRules } from "../utils/rule"; +import { FormProps } from "../utils/types"; +import { usePublicHooks } from "../../hooks"; + +const props = withDefaults(defineProps<FormProps>(), { + formInline: () => ({ + title: "鏂板", + higherDeptOptions: [], + parentId: 0, + nickname: "", + username: "", + password: "", + phone: "", + email: "", + sex: "", + status: 1, + remark: "" + }) +}); + +const sexOptions = [ + { + value: 0, + label: "鐢�" + }, + { + value: 1, + label: "濂�" + } +]; +const ruleFormRef = ref(); +const { switchStyle } = usePublicHooks(); +const newFormInline = ref(props.formInline); + +function getRef() { + return ruleFormRef.value; +} + +defineExpose({ getRef }); +</script> + +<template> + <el-form + ref="ruleFormRef" + :model="newFormInline" + :rules="formRules" + label-width="82px" + > + <el-row :gutter="30"> + <re-col :value="12" :xs="24" :sm="24"> + <el-form-item label="鐢ㄦ埛鏄电О" prop="nickname"> + <el-input + v-model="newFormInline.nickname" + clearable + placeholder="璇疯緭鍏ョ敤鎴锋樀绉�" + /> + </el-form-item> + </re-col> + <re-col :value="12" :xs="24" :sm="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 === '鏂板'" + :value="12" + :xs="24" + :sm="24" + > + <el-form-item label="鐢ㄦ埛瀵嗙爜" prop="password"> + <el-input + v-model="newFormInline.password" + clearable + placeholder="璇疯緭鍏ョ敤鎴峰瘑鐮�" + /> + </el-form-item> + </re-col> + <re-col :value="12" :xs="24" :sm="24"> + <el-form-item label="鎵嬫満鍙�" prop="phone"> + <el-input + v-model="newFormInline.phone" + clearable + placeholder="璇疯緭鍏ユ墜鏈哄彿" + /> + </el-form-item> + </re-col> + + <re-col :value="12" :xs="24" :sm="24"> + <el-form-item label="閭" prop="email"> + <el-input + v-model="newFormInline.email" + clearable + placeholder="璇疯緭鍏ラ偖绠�" + /> + </el-form-item> + </re-col> + <re-col :value="12" :xs="24" :sm="24"> + <el-form-item label="鐢ㄦ埛鎬у埆"> + <el-select + v-model="newFormInline.sex" + placeholder="璇烽�夋嫨鐢ㄦ埛鎬у埆" + class="w-full" + clearable + > + <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 :value="12" :xs="24" :sm="24"> + <el-form-item label="褰掑睘閮ㄩ棬"> + <el-cascader + v-model="newFormInline.parentId" + class="w-full" + :options="newFormInline.higherDeptOptions" + :props="{ + value: 'id', + label: 'name', + emitPath: false, + checkStrictly: true + }" + 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 === '鏂板'" + :value="12" + :xs="24" + :sm="24" + > + <el-form-item label="鐢ㄦ埛鐘舵��"> + <el-switch + v-model="newFormInline.status" + inline-prompt + :active-value="1" + :inactive-value="0" + active-text="鍚敤" + inactive-text="鍋滅敤" + :style="switchStyle" + /> + </el-form-item> + </re-col> + + <re-col> + <el-form-item label="澶囨敞"> + <el-input + v-model="newFormInline.remark" + placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�" + type="textarea" + /> + </el-form-item> + </re-col> + </el-row> + </el-form> +</template> diff --git a/src/views/system/user/form/role.vue b/src/views/system/user/form/role.vue new file mode 100644 index 0000000..19adc5b --- /dev/null +++ b/src/views/system/user/form/role.vue @@ -0,0 +1,53 @@ +<script setup lang="ts"> +import { ref } from "vue"; +import ReCol from "@/components/ReCol"; +import { RoleFormProps } from "../utils/types"; + +const props = withDefaults(defineProps<RoleFormProps>(), { + formInline: () => ({ + username: "", + nickname: "", + roleOptions: [], + ids: [] + }) +}); + +const newFormInline = ref(props.formInline); +</script> + +<template> + <el-form :model="newFormInline"> + <el-row :gutter="30"> + <!-- <re-col> + <el-form-item label="鐢ㄦ埛鍚嶇О" prop="username"> + <el-input disabled v-model="newFormInline.username" /> + </el-form-item> + </re-col> --> + <re-col> + <el-form-item label="鐢ㄦ埛鏄电О" prop="nickname"> + <el-input v-model="newFormInline.nickname" disabled /> + </el-form-item> + </re-col> + <re-col> + <el-form-item label="瑙掕壊鍒楄〃" prop="ids"> + <el-select + v-model="newFormInline.ids" + placeholder="璇烽�夋嫨" + class="w-full" + clearable + multiple + > + <el-option + v-for="(item, index) in newFormInline.roleOptions" + :key="index" + :value="item.id" + :label="item.name" + > + {{ item.name }} + </el-option> + </el-select> + </el-form-item> + </re-col> + </el-row> + </el-form> +</template> diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue new file mode 100644 index 0000000..f1841d7 --- /dev/null +++ b/src/views/system/user/index.vue @@ -0,0 +1,275 @@ +<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"; + +import Upload from "~icons/ri/upload-line"; +import Role from "~icons/ri/admin-line"; +import Password from "~icons/ri/lock-password-line"; +import More from "~icons/ep/more-filled"; +import Delete from "~icons/ep/delete"; +import EditPen from "~icons/ep/edit-pen"; +import Refresh from "~icons/ep/refresh"; +import AddFill from "~icons/ri/add-circle-line"; + +defineOptions({ + name: "SystemUser" +}); + +const treeRef = ref(); +const formRef = ref(); +const tableRef = ref(); + +const { + form, + loading, + columns, + dataList, + treeData, + treeLoading, + selectedNum, + pagination, + buttonClass, + deviceDetection, + onSearch, + resetForm, + onbatchDel, + openDialog, + onTreeSelect, + handleUpdate, + handleDelete, + handleUpload, + handleReset, + handleRole, + handleSizeChange, + onSelectionCancel, + handleCurrentChange, + handleSelectionChange +} = useUser(tableRef, treeRef); +</script> + +<template> + <div :class="['flex', 'justify-between', deviceDetection() && 'flex-wrap']"> + <tree + ref="treeRef" + :class="['mr-2', deviceDetection() ? 'w-full' : 'min-w-[200px]']" + :treeData="treeData" + :treeLoading="treeLoading" + @tree-select="onTreeSelect" + /> + <div + :class="[deviceDetection() ? ['w-full', 'mt-2'] : 'w-[calc(100%-200px)]']" + > + <el-form + ref="formRef" + :inline="true" + :model="form" + class="search-form bg-bg_color w-full 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> + + <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> +</template> + +<style lang="scss" scoped> +:deep(.el-dropdown-menu__item i) { + margin: 0; +} + +:deep(.el-button:focus-visible) { + outline: none; +} + +.main-content { + margin: 24px 24px 0 !important; +} + +.search-form { + :deep(.el-form-item) { + margin-bottom: 12px; + } +} +</style> diff --git a/src/views/system/user/svg/expand.svg b/src/views/system/user/svg/expand.svg new file mode 100644 index 0000000..bb41c35 --- /dev/null +++ b/src/views/system/user/svg/expand.svg @@ -0,0 +1 @@ +<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M22 4V2H2v2h9v14.17l-5.5-5.5-1.42 1.41L12 22l7.92-7.92-1.42-1.41-5.5 5.5V4z"/></svg> \ No newline at end of file diff --git a/src/views/system/user/svg/unexpand.svg b/src/views/system/user/svg/unexpand.svg new file mode 100644 index 0000000..04b3e9d --- /dev/null +++ b/src/views/system/user/svg/unexpand.svg @@ -0,0 +1 @@ +<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M4 2H2v20h2v-9h14.17l-5.5 5.5 1.41 1.42L22 12l-7.92-7.92-1.41 1.42 5.5 5.5H4z"/></svg> \ No newline at end of file diff --git a/src/views/system/user/tree.vue b/src/views/system/user/tree.vue new file mode 100644 index 0000000..f12261f --- /dev/null +++ b/src/views/system/user/tree.vue @@ -0,0 +1,211 @@ +<script setup lang="ts"> +import { useRenderIcon } from "@/components/ReIcon/src/hooks"; +import { ref, computed, watch, getCurrentInstance } from "vue"; + +import Dept from "~icons/ri/git-branch-line"; +// import Reset from "~icons/ri/restart-line"; +import More2Fill from "~icons/ri/more-2-fill?width=18&height=18"; +import OfficeBuilding from "~icons/ep/office-building"; +import LocationCompany from "~icons/ep/add-location"; +import ExpandIcon from "./svg/expand.svg?component"; +import UnExpandIcon from "./svg/unexpand.svg?component"; + +interface Tree { + id: number; + name: string; + highlight?: boolean; + children?: Tree[]; +} + +defineProps({ + treeLoading: Boolean, + treeData: Array +}); + +const emit = defineEmits(["tree-select"]); + +const treeRef = ref(); +const isExpand = ref(true); +const searchValue = ref(""); +const highlightMap = ref({}); +const { proxy } = getCurrentInstance(); +const defaultProps = { + children: "children", + label: "name" +}; +const buttonClass = computed(() => { + return [ + "h-[20px]!", + "text-sm!", + "reset-margin", + "text-(--el-text-color-regular)!", + "dark:text-white!", + "dark:hover:text-primary!" + ]; +}); + +const filterNode = (value: string, data: Tree) => { + if (!value) return true; + return data.name.includes(value); +}; + +function nodeClick(value) { + const nodeId = value.$treeNodeId; + highlightMap.value[nodeId] = highlightMap.value[nodeId]?.highlight + ? Object.assign({ id: nodeId }, highlightMap.value[nodeId], { + highlight: false + }) + : Object.assign({ id: nodeId }, highlightMap.value[nodeId], { + highlight: true + }); + Object.values(highlightMap.value).forEach((v: Tree) => { + if (v.id !== nodeId) { + v.highlight = false; + } + }); + emit( + "tree-select", + highlightMap.value[nodeId]?.highlight + ? Object.assign({ ...value, selected: true }) + : Object.assign({ ...value, selected: false }) + ); +} + +function toggleRowExpansionAll(status) { + isExpand.value = status; + const nodes = (proxy.$refs["treeRef"] as any).store._getAllNodes(); + for (let i = 0; i < nodes.length; i++) { + nodes[i].expanded = status; + } +} + +/** 閲嶇疆閮ㄩ棬鏍戠姸鎬侊紙閫変腑鐘舵�併�佹悳绱㈡鍊笺�佹爲鍒濆鍖栵級 */ +function onTreeReset() { + highlightMap.value = {}; + searchValue.value = ""; + toggleRowExpansionAll(true); +} + +watch(searchValue, val => { + treeRef.value!.filter(val); +}); + +defineExpose({ onTreeReset }); +</script> + +<template> + <div + v-loading="treeLoading" + 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"> + <More2Fill class="w-[28px] cursor-pointer outline-hidden" /> + <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> + <el-button + :class="buttonClass" + link + type="primary" + :icon="useRenderIcon(Reset)" + @click="onTreeReset" + > + 閲嶇疆鐘舵�� + </el-button> + </el-dropdown-item> --> + </el-dropdown-menu> + </template> + </el-dropdown> + </div> + <el-divider /> + <el-scrollbar height="calc(90vh - 88px)"> + <el-tree + ref="treeRef" + :data="treeData" + 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-sm', + '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> +</template> + +<style lang="scss" scoped> +:deep(.el-divider) { + margin: 0; +} + +:deep(.el-tree) { + --el-tree-node-hover-bg-color: transparent; +} +</style> diff --git a/src/views/system/user/utils/hook.tsx b/src/views/system/user/utils/hook.tsx new file mode 100644 index 0000000..ba17db1 --- /dev/null +++ b/src/views/system/user/utils/hook.tsx @@ -0,0 +1,535 @@ +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/ReDialog"; +import type { PaginationProps } from "@pureadmin/table"; +import ReCropperPreview from "@/components/ReCropperPreview"; +import type { FormItemProps, RoleFormItemProps } from "../utils/types"; +import { + getKeyList, + isAllEmpty, + hideTextAtIndex, + deviceDetection +} from "@pureadmin/utils"; +import { + getRoleIds, + getDeptList, + getUserList, + getAllRoleList +} from "@/api/system"; +import { + ElForm, + ElInput, + ElFormItem, + ElProgress, + ElMessageBox +} from "element-plus"; +import { + type Ref, + h, + ref, + toRaw, + watch, + computed, + reactive, + onMounted +} from "vue"; + +export function useUser(tableRef: Ref, treeRef: Ref) { + const form = reactive({ + // 宸︿晶閮ㄩ棬鏍戠殑id + deptId: "", + username: "", + phone: "", + status: "" + }); + const formRef = ref(); + const ruleFormRef = ref(); + const dataList = ref([]); + const loading = ref(true); + // 涓婁紶澶村儚淇℃伅 + const avatarInfo = ref(); + const switchLoadMap = ref({}); + const { switchStyle } = usePublicHooks(); + const higherDeptOptions = ref(); + const treeData = ref([]); + const treeLoading = ref(true); + const selectedNum = ref(0); + const pagination = reactive<PaginationProps>({ + total: 0, + pageSize: 10, + currentPage: 1, + background: true + }); + const columns: TableColumnList = [ + { + label: "鍕鹃�夊垪", // 濡傛灉闇�瑕佽〃鏍煎閫夛紝姝ゅlabel蹇呴』璁剧疆 + type: "selection", + fixed: "left", + reserveSelection: true // 鏁版嵁鍒锋柊鍚庝繚鐣欓�夐」 + }, + { + label: "鐢ㄦ埛缂栧彿", + prop: "id", + width: 90 + }, + { + label: "鐢ㄦ埛澶村儚", + prop: "avatar", + cellRenderer: ({ row }) => ( + <el-image + fit="cover" + preview-teleported={true} + src={row.avatar || userAvatar} + preview-src-list={Array.of(row.avatar || userAvatar)} + class="w-[24px] h-[24px] rounded-full align-middle" + /> + ), + width: 90 + }, + { + label: "鐢ㄦ埛鍚嶇О", + prop: "username", + minWidth: 130 + }, + { + label: "鐢ㄦ埛鏄电О", + prop: "nickname", + minWidth: 130 + }, + { + label: "鎬у埆", + prop: "sex", + minWidth: 90, + cellRenderer: ({ row, props }) => ( + <el-tag + size={props.size} + type={row.sex === 1 ? "danger" : null} + effect="plain" + > + {row.sex === 1 ? "濂�" : "鐢�"} + </el-tag> + ) + }, + { + label: "閮ㄩ棬", + prop: "dept.name", + minWidth: 90 + }, + { + label: "鎵嬫満鍙风爜", + prop: "phone", + minWidth: 90, + formatter: ({ phone }) => hideTextAtIndex(phone, { start: 3, end: 6 }) + }, + { + label: "鐘舵��", + prop: "status", + minWidth: 90, + cellRenderer: scope => ( + <el-switch + size={scope.props.size === "small" ? "small" : "default"} + loading={switchLoadMap.value[scope.index]?.loading} + v-model={scope.row.status} + active-value={1} + inactive-value={0} + active-text="宸插惎鐢�" + inactive-text="宸插仠鐢�" + inline-prompt + style={switchStyle.value} + onChange={() => onChange(scope as any)} + /> + ) + }, + { + label: "鍒涘缓鏃堕棿", + minWidth: 90, + prop: "createTime", + formatter: ({ createTime }) => + dayjs(createTime).format("YYYY-MM-DD HH:mm:ss") + }, + { + label: "鎿嶄綔", + fixed: "right", + width: 180, + slot: "operation" + } + ]; + const buttonClass = computed(() => { + return [ + "h-[20px]!", + "reset-margin", + "text-gray-500!", + "dark:text-white!", + "dark:hover:text-primary!" + ]; + }); + // 閲嶇疆鐨勬柊瀵嗙爜 + const pwdForm = reactive({ + newPwd: "" + }); + const pwdProgress = [ + { color: "#e74242", text: "闈炲父寮�" }, + { color: "#EFBD47", text: "寮�" }, + { color: "#ffa500", text: "涓�鑸�" }, + { color: "#1bbf1b", text: "寮�" }, + { color: "#008000", text: "闈炲父寮�" } + ]; + // 褰撳墠瀵嗙爜寮哄害锛�0-4锛� + const curScore = ref(); + const roleOptions = ref([]); + + function onChange({ row, index }) { + ElMessageBox.confirm( + `纭瑕�<strong>${ + row.status === 0 ? "鍋滅敤" : "鍚敤" + }</strong><strong style='color:var(--el-color-primary)'>${ + row.username + }</strong>鐢ㄦ埛鍚�?`, + "绯荤粺鎻愮ず", + { + confirmButtonText: "纭畾", + cancelButtonText: "鍙栨秷", + type: "warning", + dangerouslyUseHTMLString: true, + draggable: true + } + ) + .then(() => { + switchLoadMap.value[index] = Object.assign( + {}, + switchLoadMap.value[index], + { + loading: true + } + ); + setTimeout(() => { + switchLoadMap.value[index] = Object.assign( + {}, + switchLoadMap.value[index], + { + loading: false + } + ); + message("宸叉垚鍔熶慨鏀圭敤鎴风姸鎬�", { + type: "success" + }); + }, 300); + }) + .catch(() => { + row.status === 0 ? (row.status = 1) : (row.status = 0); + }); + } + + function handleUpdate(row) { + console.log(row); + } + + function handleDelete(row) { + message(`鎮ㄥ垹闄や簡鐢ㄦ埛缂栧彿涓�${row.id}鐨勮繖鏉℃暟鎹甡, { type: "success" }); + onSearch(); + } + + function handleSizeChange(val: number) { + console.log(`${val} items per page`); + } + + function handleCurrentChange(val: number) { + console.log(`current page: ${val}`); + } + + /** 褰揅heckBox閫夋嫨椤瑰彂鐢熷彉鍖栨椂浼氳Е鍙戣浜嬩欢 */ + function handleSelectionChange(val) { + selectedNum.value = val.length; + // 閲嶇疆琛ㄦ牸楂樺害 + tableRef.value.setAdaptive(); + } + + /** 鍙栨秷閫夋嫨 */ + function onSelectionCancel() { + selectedNum.value = 0; + // 鐢ㄤ簬澶氶�夎〃鏍硷紝娓呯┖鐢ㄦ埛鐨勯�夋嫨 + tableRef.value.getTableRef().clearSelection(); + } + + /** 鎵归噺鍒犻櫎 */ + function onbatchDel() { + // 杩斿洖褰撳墠閫変腑鐨勮 + const curSelected = tableRef.value.getTableRef().getSelectionRows(); + // 鎺ヤ笅鏉ユ牴鎹疄闄呬笟鍔★紝閫氳繃閫変腑琛岀殑鏌愰」鏁版嵁锛屾瘮濡備笅闈㈢殑id锛岃皟鐢ㄦ帴鍙h繘琛屾壒閲忓垹闄� + message(`宸插垹闄ょ敤鎴风紪鍙蜂负 ${getKeyList(curSelected, "id")} 鐨勬暟鎹甡, { + type: "success" + }); + tableRef.value.getTableRef().clearSelection(); + onSearch(); + } + + async function onSearch() { + loading.value = true; + const { data } = await getUserList(toRaw(form)); + dataList.value = data.list; + pagination.total = data.total; + pagination.pageSize = data.pageSize; + pagination.currentPage = data.currentPage; + + setTimeout(() => { + loading.value = false; + }, 500); + } + + const resetForm = formEl => { + if (!formEl) return; + formEl.resetFields(); + form.deptId = ""; + treeRef.value.onTreeReset(); + onSearch(); + }; + + function onTreeSelect({ id, selected }) { + form.deptId = selected ? id : ""; + onSearch(); + } + + function formatHigherDeptOptions(treeList) { + // 鏍规嵁杩斿洖鏁版嵁鐨剆tatus瀛楁鍊煎垽鏂拷鍔犳槸鍚︾鐢╠isabled瀛楁锛岃繑鍥炲鐞嗗悗鐨勬爲缁撴瀯锛岀敤浜庝笂绾ч儴闂ㄧ骇鑱旈�夋嫨鍣ㄧ殑灞曠ず锛堝疄闄呭紑鍙戜腑涔熸槸濡傛锛屼笉鍙兘鍓嶇闇�瑕佺殑姣忎釜瀛楁鍚庣閮戒細杩斿洖锛岃繖鏃堕渶瑕佸墠绔嚜琛屾牴鎹悗绔繑鍥炵殑鏌愪簺瀛楁鍋氶�昏緫澶勭悊锛� + if (!treeList || !treeList.length) return; + const newTreeList = []; + for (let i = 0; i < treeList.length; i++) { + treeList[i].disabled = treeList[i].status === 0 ? true : false; + formatHigherDeptOptions(treeList[i].children); + newTreeList.push(treeList[i]); + } + return newTreeList; + } + + function openDialog(title = "鏂板", row?: FormItemProps) { + addDialog({ + title: `${title}鐢ㄦ埛`, + props: { + formInline: { + title, + higherDeptOptions: formatHigherDeptOptions(higherDeptOptions.value), + parentId: row?.dept.id ?? 0, + nickname: row?.nickname ?? "", + username: row?.username ?? "", + password: row?.password ?? "", + phone: row?.phone ?? "", + email: row?.email ?? "", + sex: row?.sex ?? "", + status: row?.status ?? 1, + remark: row?.remark ?? "" + } + }, + width: "46%", + draggable: true, + fullscreen: deviceDetection(), + fullscreenIcon: true, + closeOnClickModal: false, + contentRenderer: () => h(editForm, { ref: formRef, formInline: null }), + beforeSure: (done, { options }) => { + const FormRef = formRef.value.getRef(); + const curData = options.props.formInline as FormItemProps; + function chores() { + message(`鎮�${title}浜嗙敤鎴峰悕绉颁负${curData.username}鐨勮繖鏉℃暟鎹甡, { + type: "success" + }); + done(); // 鍏抽棴寮规 + onSearch(); // 鍒锋柊琛ㄦ牸鏁版嵁 + } + FormRef.validate(valid => { + if (valid) { + console.log("curData", curData); + // 琛ㄥ崟瑙勫垯鏍¢獙閫氳繃 + if (title === "鏂板") { + // 瀹為檯寮�鍙戝厛璋冪敤鏂板鎺ュ彛锛屽啀杩涜涓嬮潰鎿嶄綔 + chores(); + } else { + // 瀹為檯寮�鍙戝厛璋冪敤淇敼鎺ュ彛锛屽啀杩涜涓嬮潰鎿嶄綔 + chores(); + } + } + }); + } + }); + } + + const cropRef = ref(); + /** 涓婁紶澶村儚 */ + function handleUpload(row) { + addDialog({ + title: "瑁佸壀銆佷笂浼犲ご鍍�", + width: "40%", + closeOnClickModal: false, + fullscreen: deviceDetection(), + contentRenderer: () => + h(ReCropperPreview, { + ref: cropRef, + imgSrc: row.avatar || userAvatar, + onCropper: info => (avatarInfo.value = info) + }), + beforeSure: done => { + console.log("瑁佸壀鍚庣殑鍥剧墖淇℃伅锛�", avatarInfo.value); + // 鏍规嵁瀹為檯涓氬姟浣跨敤avatarInfo.value鍜宺ow閲岀殑鏌愪簺瀛楁鍘昏皟鐢ㄤ笂浼犲ご鍍忔帴鍙e嵆鍙� + done(); // 鍏抽棴寮规 + onSearch(); // 鍒锋柊琛ㄦ牸鏁版嵁 + }, + closeCallBack: () => cropRef.value.hidePopover() + }); + } + + watch( + pwdForm, + ({ newPwd }) => + (curScore.value = isAllEmpty(newPwd) ? -1 : zxcvbn(newPwd).score) + ); + + /** 閲嶇疆瀵嗙爜 */ + function handleReset(row) { + addDialog({ + title: `閲嶇疆 ${row.username} 鐢ㄦ埛鐨勫瘑鐮乣, + width: "30%", + draggable: true, + closeOnClickModal: false, + fullscreen: deviceDetection(), + contentRenderer: () => ( + <> + <ElForm ref={ruleFormRef} model={pwdForm}> + <ElFormItem + prop="newPwd" + rules={[ + { + required: true, + message: "璇疯緭鍏ユ柊瀵嗙爜", + trigger: "blur" + } + ]} + > + <ElInput + clearable + show-password + type="password" + v-model={pwdForm.newPwd} + placeholder="璇疯緭鍏ユ柊瀵嗙爜" + /> + </ElFormItem> + </ElForm> + <div class="my-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鍜宺ow閲岀殑鏌愪簺瀛楁鍘昏皟鐢ㄩ噸缃敤鎴峰瘑鐮佹帴鍙e嵆鍙� + 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鍜宺ow閲岀殑鏌愪簺瀛楁鍘昏皟鐢ㄤ慨鏀硅鑹叉帴鍙e嵆鍙� + done(); // 鍏抽棴寮规 + } + }); + } + + onMounted(async () => { + treeLoading.value = true; + onSearch(); + + // 褰掑睘閮ㄩ棬 + const { data } = await getDeptList(); + higherDeptOptions.value = handleTree(data); + treeData.value = handleTree(data); + treeLoading.value = false; + + // 瑙掕壊鍒楄〃 + roleOptions.value = (await getAllRoleList()).data; + }); + + return { + form, + loading, + columns, + dataList, + treeData, + treeLoading, + selectedNum, + pagination, + buttonClass, + deviceDetection, + onSearch, + resetForm, + onbatchDel, + openDialog, + onTreeSelect, + handleUpdate, + handleDelete, + handleUpload, + handleReset, + handleRole, + handleSizeChange, + onSelectionCancel, + handleCurrentChange, + handleSelectionChange + }; +} diff --git a/src/views/system/user/utils/reset.css b/src/views/system/user/utils/reset.css new file mode 100644 index 0000000..97f4e4f --- /dev/null +++ b/src/views/system/user/utils/reset.css @@ -0,0 +1,5 @@ +/** 灞�閮ㄩ噸缃� ElProgress 鐨勯儴鍒嗘牱寮� */ +.el-progress-bar__outer, +.el-progress-bar__inner { + border-radius: 0; +} diff --git a/src/views/system/user/utils/rule.ts b/src/views/system/user/utils/rule.ts new file mode 100644 index 0000000..f946ee2 --- /dev/null +++ b/src/views/system/user/utils/rule.ts @@ -0,0 +1,39 @@ +import { reactive } from "vue"; +import type { FormRules } from "element-plus"; +import { isPhone, isEmail } 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" + } + ] +}); diff --git a/src/views/system/user/utils/types.ts b/src/views/system/user/utils/types.ts new file mode 100644 index 0000000..c5ab88c --- /dev/null +++ b/src/views/system/user/utils/types.ts @@ -0,0 +1,36 @@ +interface FormItemProps { + id?: number; + /** 鐢ㄤ簬鍒ゆ柇鏄痐鏂板`杩樻槸`淇敼` */ + title: string; + higherDeptOptions: Record<string, unknown>[]; + parentId: number; + nickname: string; + username: string; + password: string; + phone: string | number; + email: string; + sex: string | number; + status: number; + dept?: { + id?: number; + name?: string; + }; + remark: string; +} +interface FormProps { + formInline: FormItemProps; +} + +interface RoleFormItemProps { + username: string; + nickname: string; + /** 瑙掕壊鍒楄〃 */ + roleOptions: any[]; + /** 閫変腑鐨勮鑹插垪琛� */ + ids: Record<number, unknown>[]; +} +interface RoleFormProps { + formInline: RoleFormItemProps; +} + +export type { FormItemProps, FormProps, RoleFormItemProps, RoleFormProps }; -- Gitblit v1.9.1