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