@dongkboy 1 жил өмнө
parent
commit
7f04eeefce

+ 1 - 1
.env.development

@@ -4,7 +4,7 @@ VITE_APP_SETTING = true
 VITE_APP_TITLE = 掌柜运营平台
 # 接口请求地址,会设置到 axios 的 baseURL 参数上
 # VITE_APP_API_BASEURL = https://test.baoxianzhanggui.com/web-api #默认地址
-VITE_APP_API_BASEURL = http://192.168.5.48:8001  #田智
+VITE_APP_API_BASEURL = http://192.168.5.148:8001  #田智
 
 # 调试工具,可设置 eruda 或 vconsole,如果不需要开启则留空
 VITE_APP_DEBUG_TOOL =

+ 6 - 1
src/api/index.ts

@@ -52,6 +52,11 @@ axiosInstance.interceptors.response.use(
         return Promise.resolve(response.data)
       }
     }
+    else if(response.data.code === 400){
+      Message.error(response.data.msg, {
+        zIndex: 2000,
+      })
+    }
     else {
       if (response.data.code === 401) {
         Message({
@@ -59,7 +64,7 @@ axiosInstance.interceptors.response.use(
           type: 'error',
           duration: 2000,
         })
-        // useUserStore().logout()
+        useUserStore().logout()
       }
       // useUserStore().logout()
     }

+ 14 - 3
src/api/modules/role.ts

@@ -1,14 +1,25 @@
 import { ApiInstance } from '../type'
 interface roleData {
   name: string;
-  desc: string;
+  remark: string;
   system: string,
-  permsId: number[],
-  perms: string[]
+  permsId: (number| string)[],
+  perms: object[]
+}
+interface roleList {
+  pageNum: number;
+  pageSize: number;
 }
 const roleApi = (axiosInstance: ApiInstance) => ({
   // 新增角色
   roleAdd: (data: roleData) => axiosInstance.post('userRole/roleAdd', data, {}),
+  // 新增角色
+  roleUpdate: (data: roleData) => axiosInstance.post('userRole/roleUpdate', data, {}),
+  //角色列表
+  roleList: (data: roleList) => axiosInstance.post('userRole/roleList ', data, {}),
+  //详情
+  roleGet: (params: Record<string, any>) => axiosInstance.get('userRole/roleGet', { params }),
+
 })
 
 export default roleApi

BIN
src/assets/images/loginback.png


BIN
src/assets/images/loginback1.png


+ 1 - 0
src/assets/styles/globals.css

@@ -226,6 +226,7 @@ textarea {
 
 .vh100 {
   height: calc(100vh - 200px);
+  overflow-y: auto;
 }
 
 .strong {

+ 65 - 25
src/components/LoginForm/index.vue

@@ -22,7 +22,7 @@ const emits = defineEmits<{
 }>()
 
 const userStore = useUserStore()
-const title = import.meta.env.VITE_APP_TITLE
+// const title = import.meta.env.VITE_APP_TITLE
 const baseurl = import.meta.env.VITE_APP_API_BASEURL
 const loading = ref(false)
 const remember = ref(false)
@@ -85,48 +85,88 @@ function handleLogin() {
   })
 }
 
-function testAccount(userName: string) {
-  form.value.userName = userName
-  form.value.passWord = '123456'
-  handleLogin()
-}
+// function testAccount(userName: string) {
+//   form.value.userName = userName
+//   form.value.passWord = '123456'
+//   handleLogin()
+// }
+
 </script>
+<style lang="scss" scoped>
+:deep(.el-form-item--large) {
+  margin-bottom: 30px;
+}
+
+:deep(.el-input__prefix) {
+  color: #3275ff;
+}
+
+.title {
+  position: relative;
+  margin-bottom: 56px;
+  font-size: 36px;
+  color: #333;
 
+  span {
+    position: relative;
+    z-index: 9999;
+    letter-spacing: 5px;
+  }
+
+  .tag {
+    position: absolute;
+    bottom: 0;
+    width: 76px;
+    height: 8px;
+    background: linear-gradient(90deg, #4fcaff 0%, #2b62ff 100%);
+  }
+}
+
+.loginCSS {
+  padding: 2px;
+  font-size: 20px;
+  background: linear-gradient(91deg, #4fcaff 0%, #2b62ff 100%);
+}
+</style>
 <template>
-  <ElForm ref="formRef" :model="form" :rules="rules" class="min-h-500px w-full flex-col-stretch-center p-12"
-    size="large">
-    <div class="mb-6">
+  <ElForm ref="formRef" :model="form" :rules="rules" class="min-h-500px w-full flex-col-stretch-center "
+    style="padding: 81px 75px;" size="large">
+    <!-- <div class="mb-6">
       <HTabList v-model="type" :options="[
         { label: '账号密码登录', value: 'default' },
         { label: '扫码登录', value: 'qrcode' },
       ]" />
-    </div>
+    </div> -->
     <template v-if="type === 'default'">
-      <h3 class="mb-8 text-xl color-[var(--el-text-color-primary)] font-bold">
+      <!-- <h3 class="mb-8 text-xl color-[var(--el-text-color-primary)] font-bold">
         欢迎使用 {{ title }} ! 👋🏻
-      </h3>
+      </h3> -->
+      <div class="title strong">
+        <span>登录</span>
+        <div class="tag"></div>
+      </div>
       <div>
         <ElFormItem prop="userName">
-          <ElInput v-model="form.userName" placeholder="用户名" type="text" tabindex="1">
+          <ElInput v-model="form.userName" placeholder="手机号或工号" type="text" tabindex="1">
             <template #prefix>
-              <SvgIcon name="i-ri:user-3-fill" />
+              <el-icon><User /></el-icon>
             </template>
           </ElInput>
         </ElFormItem>
         <ElFormItem prop="passWord">
-          <ElInput v-model="form.passWord" type="password" placeholder="密码" tabindex="2" show-password
+          <ElInput v-model="form.passWord" type="password" placeholder="登录密码" tabindex="2" show-password
             @keyup.enter="handleLogin">
             <template #prefix>
-              <SvgIcon name="i-ri:lock-2-fill" />
+              <el-icon><Lock /></el-icon>
             </template>
           </ElInput>
         </ElFormItem>
 
-        <ElFormItem prop="captcha">
+        <ElFormItem prop="captcha" style="margin-bottom: 0;">
           <ElCol :md="14" :sm="14" :xs="14">
             <ElInput v-model="form.captcha" placeholder="验证码" type="text" tabindex="3">
               <template #prefix>
-                <SvgIcon name="i-ri:user-3-fill" />
+                <el-icon><CircleCheck /></el-icon>
               </template>
             </ElInput>
           </ElCol>
@@ -136,23 +176,23 @@ function testAccount(userName: string) {
           </ElCol>
         </ElFormItem>
       </div>
-      <div class="mb-4 flex-center-between">
+      <div class="mb-17 flex-center-between">
         <ElCheckbox v-model="remember">
           记住密码
         </ElCheckbox>
         <ElLink type="primary" :underline="false" @click="emits('onResetPassword', form.userName)">
-          忘记密码了?
+          忘记密码
         </ElLink>
       </div>
-      <ElButton :loading="loading" type="primary" size="large" style="width: 100%;" @click.prevent="handleLogin">
+      <ElButton class="loginCSS" :loading="loading" type="primary"  style="width: 100%;" @click.prevent="handleLogin">
         登录
       </ElButton>
-      <div class="mt-4 flex-center gap-2 text-sm color-[var(--el-text-color-secondary)]">
+      <!-- <div class="mt-4 flex-center gap-2 text-sm color-[var(--el-text-color-secondary)]">
         还没有帐号?
         <ElLink type="primary" :underline="false" @click="emits('onRegister', form.userName)">
           创建新帐号
         </ElLink>
-      </div>
+      </div> -->
     </template>
     <template v-else-if="type === 'qrcode'">
       <div class="flex-col-center">
@@ -162,7 +202,7 @@ function testAccount(userName: string) {
         </div>
       </div>
     </template>
-    <div class="mt-4 text-center -mb-4">
+    <!-- <div class="mt-4 text-center -mb-4">
       <ElDivider>演示账号一键登录</ElDivider>
       <ElButton type="primary" size="small" plain @click="testAccount('admin')">
         admin
@@ -170,6 +210,6 @@ function testAccount(userName: string) {
       <ElButton size="small" plain @click="testAccount('test')">
         test
       </ElButton>
-    </div>
+    </div> -->
   </ElForm>
 </template>

+ 39 - 0
src/components/PaginationWrapper/index.vue

@@ -0,0 +1,39 @@
+<template>
+  <div class="pad-1">
+    <el-pagination :current-page="currentPage" :page-size="pageSize" :page-sizes="[20, 40, 60, 80]" :total="total"
+      :size="size" @size-change="handleSizeChange" @current-change="handlePageChange"
+      layout="total, sizes, prev, pager, next" :disabled="disabled" :background="background" />
+  </div>
+
+</template>
+
+<script setup lang="ts">
+import { ref, defineProps, defineEmits } from 'vue';
+import { ElPagination } from 'element-plus';
+import type { ComponentSize } from 'element-plus'
+interface Props {
+  pageNum: number;
+  pageSize: number;
+  total: number;
+
+}
+const background = ref(false)
+const disabled = ref(false)
+const size = ref<ComponentSize>('small')
+const props = defineProps<Props>();
+const emit = defineEmits(['pageChange','sizeChange']);
+
+const currentPage = ref(props.pageNum);
+const pageSize = ref(props.pageSize);
+const total = props.total;
+const handlePageChange = (newPage: number) => {
+  currentPage.value = newPage;
+  emit('pageChange', newPage);
+};
+const handleSizeChange = (val: number) => {
+  pageSize.value = val;
+  emit('sizeChange', val);
+
+};
+
+</script>

+ 3 - 0
src/types/components.d.ts

@@ -8,6 +8,7 @@ export {}
 declare module 'vue' {
   export interface GlobalComponents {
     Auth: typeof import('./../components/Auth/index.vue')['default']
+    ELPagination: typeof import('./../components/ELPagination/index.vue')['default']
     FileUpload: typeof import('./../components/FileUpload/index.vue')['default']
     FixedActionBar: typeof import('./../components/FixedActionBar/index.vue')['default']
     HButton: typeof import('./../layouts/ui-kit/HButton.vue')['default']
@@ -29,6 +30,8 @@ declare module 'vue' {
     NotAllowed: typeof import('./../components/NotAllowed/index.vue')['default']
     PageHeader: typeof import('./../components/PageHeader/index.vue')['default']
     PageMain: typeof import('./../components/PageMain/index.vue')['default']
+    Pagination: typeof import('./../components/Pagination/index.vue')['default']
+    PaginationWrapper: typeof import('./../components/PaginationWrapper/index.vue')['default']
     PcasCascader: typeof import('./../components/PcasCascader/index.vue')['default']
     RegisterForm: typeof import('./../components/RegisterForm/index.vue')['default']
     ResetPasswordForm: typeof import('./../components/ResetPasswordForm/index.vue')['default']

+ 5 - 4
src/utils/composables/routeUtils.ts

@@ -26,11 +26,11 @@ export interface TreeNode {
   [key: string]: any; // 允许任意其他属性
 }
 
-let idCounter = 0;
+let idCounter = ref(0);
 
 // 生成唯一 ID
 const generateId = (): number => {
-  return ++idCounter;
+  return ++idCounter.value;
 };
 
 // 预处理函数:确保 children 和 auth 类型正确
@@ -73,7 +73,8 @@ const parseAuthToChildren = (authList: string[]): TreeNode[] => {
 };
 
 // 将 RouteItem 转换为 TreeNode
-export const convertToTreeData = (routes: RouteItem[]): TreeNode[] => {
+export const convertToTreeData = (id:number,routes: RouteItem[]): TreeNode[] => {
+  idCounter.value=id;
   return routes.map(route => {
     const node: TreeNode = {
       id: generateId(),
@@ -83,7 +84,7 @@ export const convertToTreeData = (routes: RouteItem[]): TreeNode[] => {
     };
 
     if (route.children && route.children.length > 0) {
-      node.children = convertToTreeData(route.children);
+      node.children = convertToTreeData(idCounter.value,route.children);
     }
 
     if (route.meta?.auth) {

+ 17 - 4
src/views/login.vue

@@ -25,14 +25,17 @@ const formType = ref<'login' | 'register' | 'resetPassword'>('login')
 
 <template>
   <div class="bg-banner" />
+  <img class="bgback" src=".././assets/images/loginback1.png" alt="">
+
   <div class="login-box">
-    <div class="login-banner">
+    <!-- <div class="login-banner">
       <img
         src="@/assets/images/logo.png"
         class="absolute left-4 top-4 h-30px rounded ring ring-stone-2 dark-ring-stone-8"
       >
       <img src="@/assets/images/login-banner.png" class="banner">
-    </div>
+    </div> -->
+
     <div class="login-form flex-col-center">
       <Transition name="fade" mode="out-in">
         <LoginForm
@@ -53,13 +56,23 @@ const formType = ref<'login' | 'register' | 'resetPassword'>('login')
   </div>
 </template>
 
-<style scoped>
+<style lang="scss" scoped>
 .bg-banner {
   position: fixed;
   z-index: 0;
   width: 100%;
   height: 100%;
   background: radial-gradient(circle at center, var(--g-container-bg), var(--g-bg));
+  background-image: url(".././assets/images/loginback.png");
+  background-size: cover;
+}
+
+.bgback {
+  position: fixed;
+  top: 58%;
+  left: 35%;
+  z-index: 0;
+  transform: translateX(-50%) translateY(-50%);
 }
 
 [data-mode="mobile"] {
@@ -103,7 +116,7 @@ const formType = ref<'login' | 'register' | 'resetPassword'>('login')
 
   [data-mode="pc"] & {
     top: 50%;
-    left: 50%;
+    left: 70%;
     border-radius: 10px;
     box-shadow: var(--el-box-shadow);
     transform: translateX(-50%) translateY(-50%);

+ 112 - 27
src/views/personnel/roleManagement.vue

@@ -15,7 +15,15 @@
               <ElButton type="primary">查找</ElButton>
             </ElCol>
           </ElFormItem>
-
+          <div style="height: 500px;overflow-y: auto;">
+            <div class="pad-2 roleitem flex f-c " v-for="(item, index) in roleList" :key="index"
+              @click="roleitemClick(item.id)">
+              <span>{{ item.name }}</span>
+              <span style="margin-top: 10px;font-size: 12px;color: #999;">{{ item.remark }}</span>
+            </div>
+          </div>
+          <PaginationWrapper v-if="totalSize > 0" style="position: absolute;bottom: 0;" :pageNum="PageNum"
+            :pageSize="PageSize" @pageChange="pageChange" @sizeChange="sizeChange" :total="totalSize" />
         </PageMain>
       </ElCol>
       <ElCol :md="18">
@@ -35,16 +43,17 @@
               <ElInput v-model="roleInfo.name" placeholder="输入20个字以内的角色名称" type="text" tabindex="1">
               </ElInput>
             </ElFormItem>
-            <ElFormItem prop="desc" label="角色描述">
-              <ElInput v-model="roleInfo.desc" placeholder="请输入" type="textarea" tabindex="2">
+            <ElFormItem label="角色描述">
+              <ElInput v-model="roleInfo.remark" placeholder="请输入" type="textarea" tabindex="2">
               </ElInput>
             </ElFormItem>
           </ElForm>
           <ElDivider></ElDivider>
           <div>
             <h5>角色权限</h5>
-            <ElTree style="max-height: 350px;overflow-y: auto;" :data="treeData" :props="defaultProps" show-checkbox
-              node-key="id" :default-expand-all="true" :default-checked-keys="[6]" @check="handleCheckChange" />
+            <ElTree style="max-height: 350px;overflow-y: auto;" ref="treeRef" :data="treeData" :props="defaultProps"
+              show-checkbox node-key="id" :default-expand-all="true" :default-checked-keys="checkedKeys"
+              @check="handleCheckChange" />
           </div>
         </PageMain>
       </ElCol>
@@ -55,24 +64,48 @@
 
 <script lang='ts' setup>
 import { asyncRoutes } from '@/router/routes' // 请根据实际路径调整
-import { ref, reactive, onMounted } from 'vue';
-import type { FormRules } from 'element-plus';
+import { ref, reactive, onBeforeMount } from 'vue';
+import type { FormRules, ElTree } from 'element-plus';
 import { preprocessRoutes, convertToTreeData, TreeNode } from '@/utils/composables/routeUtils'; // 导入工具方法
 import api from '@/api'
+import Message from 'vue-m-message'
+
+onBeforeMount(() => {
+  rolequery()//角色列表
+  // 对 asyncRoutes 进行预处理并转换为树形结构
+  const processedRoutes = preprocessRoutes(asyncRoutes);
+  treeData.value = convertToTreeData(0, processedRoutes);
+});
+interface roleList {
+  id: string,
+  name: string,
+  remark: string,
+  [key: string]: any
+}
+interface Perm {
+  id: string; // 根据实际情况,id 可能是数字或字符串
+}
+const treeRef = ref<InstanceType<typeof ElTree>>();
 const roleName = ref<string>("");
+const PageNum = ref<number>(1);
+const PageSize = ref<number>(20);
+const checkedKeys = ref<string[]>([])
+const totalSize = ref(0)
 
 const roleInfo = reactive<{
   name: string;
-  desc: string;
+  remark: string;
   system: string,
-  permsId: number[],
-  perms: string[]
+  permsId: (number | string)[],
+  perms: object[],
+  id?: string,
 }>({
   name: "",//角色
-  desc: "",//描述
+  remark: "",//描述
   system: "PLATFORM",//系统
   permsId: [],//权限id
   perms: [],//权限标识
+  id: '',
 });
 const roleRules = ref<FormRules>({
   name: [
@@ -80,6 +113,7 @@ const roleRules = ref<FormRules>({
   ],
 
 });
+const roleList = ref<roleList[]>([])
 const treeData = ref<TreeNode[]>([]);
 const defaultProps = {
   children: 'children',
@@ -94,33 +128,79 @@ const handleCheckChange = (
     halfCheckedNodes: TreeNode[];
   }
 ) => {
-  // console.log('Checked keys:', checkedInfo.checkedKeys);
-  // console.log(checkedInfo.checkedNodes);
   roleInfo.permsId = checkedInfo.checkedKeys;
   roleInfo.perms = [];
-  checkedInfo.checkedNodes.forEach(val => {
-    if (val.type === '2' ) {
-      roleInfo.perms.push(val.auth);
-    }
-  });
+  roleInfo.perms = checkedInfo.checkedNodes;
+
 };
-   function roleSubmit() {
-    api.roleApi.roleAdd(roleInfo).then((res)=>{
-      console.log(res)
+
+async function roleSubmit() {
+  if (roleInfo.id) {
+    await api.roleApi.roleUpdate(roleInfo).then((res: any) => {
+      if (res.code == 200) {
+        rolequery();
+        Message.success(res.msg, {
+          zIndex: 2000,
+        })
+        resetForm();
+      }
+
+    })
+  } else {
+    await api.roleApi.roleAdd(roleInfo).then((res:any) => {
+      if (res.code == 200) {
+        rolequery();
+        Message.success(res.msg, {
+          zIndex: 2000,
+        })
+        resetForm();
+      }
     })
   }
-onMounted(() => {
-  // 对 asyncRoutes 进行预处理并转换为树形结构
-  const processedRoutes = preprocessRoutes(asyncRoutes);
-  treeData.value = convertToTreeData(processedRoutes);
 
-});
+}
+function pageChange(e: number) {
+  PageNum.value = e;
+  PageSize.value = 20;
+}
+function sizeChange(val: number) {
+  PageSize.value = val;
+}
+function roleitemClick(id: string) {
+  api.roleApi.roleGet({ roleId: id }).then((res: any) => {
+    if (res.code === 200) {
+      checkedKeys.value = res.data.perms.map((val: Perm) => val.id);
+      roleInfo.permsId = res.data.perms.map((val: Perm) => val.id);
+      roleInfo.perms = res.data.perms;
+      roleInfo.name = res.data.name;
+      roleInfo.remark = res.data.remark;
+      roleInfo.id = res.data.id;
+    }
+  })
+}
+async function rolequery() {
+  await api.roleApi.roleList({ pageNum: PageNum.value, pageSize: PageSize.value }).then((res: any) => {
+    totalSize.value = res.data.total;
+    roleList.value = res.data.records;
+  })
+}
+function resetForm() {
+  roleInfo.name = "";
+  roleInfo.remark = "";
+  roleInfo.system = "PLATFORM";
+  roleInfo.permsId = [];
+  roleInfo.perms = [];
+  roleInfo.id = '';
+  if (treeRef.value) {
+    treeRef.value.setCheckedKeys([]);
+  }
+}
+
 
 </script>
 
 <style lang="scss" scoped>
 .roleitem {
-  padding: 20px;
   cursor: pointer;
   background: white;
 }
@@ -133,4 +213,9 @@ onMounted(() => {
 .active {
   background: #b4b2b2;
 }
+
+.el-pagination {
+  position: absolute;
+  bottom: 0;
+}
 </style>