刘洋 1 år sedan
förälder
incheckning
0b74741492

+ 18 - 0
src/api/user.js

@@ -54,6 +54,23 @@ export const getAllMenuResource = () =>
   request({
   request({
     url: '/api/admin/sys/privilege/list',
     url: '/api/admin/sys/privilege/list',
   });
   });
+
+export const addRole = (data) =>
+  request({
+    url: '/api/admin/role/save',
+    data,
+  });
+export const deleteRole = (data) =>
+  request({
+    url: '/api/admin/role/delete',
+    params: data,
+    loading: true,
+  });
+export const getRoleDetail = (data) =>
+  request({
+    url: '/api/admin/role/privilege/list',
+    params: data,
+  });
 export const logout = (data) =>
 export const logout = (data) =>
   request({
   request({
     url: '/api/logout',
     url: '/api/logout',
@@ -64,6 +81,7 @@ export const getMenus = () =>
   request({
   request({
     url: '/api/admin/common/get_menu', //真实接口url
     url: '/api/admin/common/get_menu', //真实接口url
     method: 'post',
     method: 'post',
+    loading: true,
   });
   });
 
 
 export const editUser = (data) =>
 export const editUser = (data) =>

BIN
src/assets/imgs/login_bg_grid.png


BIN
src/assets/imgs/login_bg_inner.png


BIN
src/assets/imgs/login_logo.png


BIN
src/assets/imgs/user_head.png


+ 32 - 1
src/layout/index.vue

@@ -34,14 +34,27 @@
           </div>
           </div>
           <div class="header-right flex items-center">
           <div class="header-right flex items-center">
             <t-dropdown
             <t-dropdown
+              class="m-r-20px"
               :options="colorOptions"
               :options="colorOptions"
-              trigger="click"
+              trigger="hover"
               @click="colorChoose"
               @click="colorChoose"
             >
             >
               <t-button theme="default" variant="outline" shape="square">
               <t-button theme="default" variant="outline" shape="square">
                 <t-icon name="logo-windows-filled" size="16"
                 <t-icon name="logo-windows-filled" size="16"
               /></t-button>
               /></t-button>
             </t-dropdown>
             </t-dropdown>
+            <t-dropdown
+              trigger="hover"
+              :options="userOptions"
+              @click="clickHandler"
+            >
+              <div class="flex items-center cursor-pointer">
+                <div class="head-img-box flex justify-center items-center">
+                  <img src="../assets/imgs/user_head.png" />
+                </div>
+                <span class="real-name">{{ userStore.user?.realName }}</span>
+              </div>
+            </t-dropdown>
           </div>
           </div>
         </div>
         </div>
       </t-header>
       </t-header>
@@ -64,6 +77,7 @@ import LeftMenu from './left-menu.vue';
 import { Color } from 'tvision-color';
 import { Color } from 'tvision-color';
 import { generateColorMap, insertThemeStylesheet } from '@/config/color';
 import { generateColorMap, insertThemeStylesheet } from '@/config/color';
 import { moduleMap } from '@/router/asyncRoutes';
 import { moduleMap } from '@/router/asyncRoutes';
+import { ChevronDownIcon, UserIcon } from 'tdesign-icons-vue-next';
 
 
 const router = useRouter();
 const router = useRouter();
 const route = useRoute();
 const route = useRoute();
@@ -87,6 +101,7 @@ const colorOptions = ref([
   { content: '橙色主题', value: '#e78b24' },
   { content: '橙色主题', value: '#e78b24' },
   { content: '红色主题', value: '#dd4814' },
   { content: '红色主题', value: '#dd4814' },
 ]);
 ]);
+const userOptions = ref([{ content: '修改密码', value: '1' }]);
 const colorChoose = (data) => {
 const colorChoose = (data) => {
   console.log('data:', data);
   console.log('data:', data);
   const hex = data.value;
   const hex = data.value;
@@ -112,6 +127,22 @@ const colorChoose = (data) => {
     border-bottom: 1px solid #eee;
     border-bottom: 1px solid #eee;
     .header-wrap {
     .header-wrap {
       padding: 0 10px;
       padding: 0 10px;
+      padding-right: 20px;
+      .real-name {
+        color: @light-text-color;
+      }
+      .head-img-box {
+        width: 32px;
+        height: 32px;
+        border-radius: 16px;
+        overflow: hidden;
+        margin-right: 5px;
+
+        img {
+          width: 100%;
+          height: 100%;
+        }
+      }
     }
     }
   }
   }
   .layout-content {
   .layout-content {

+ 2 - 0
src/style/color.less

@@ -0,0 +1,2 @@
+@dark-text-color: #262626;
+@light-text-color: #8c8c8c;

+ 4 - 4
src/style/tdesign-reset.less

@@ -11,18 +11,18 @@
   // margin-bottom: 15px;
   // margin-bottom: 15px;
 }
 }
 .t-dialog--default {
 .t-dialog--default {
-  // padding-top: 20px;
+  padding-top: 20px;
 }
 }
 .t-dialog__body {
 .t-dialog__body {
-  // padding-top: 25px;
+  padding-top: 25px;
 }
 }
 
 
 .t-date-picker__panel .t-pagination-mini .t-pagination-mini__current {
 .t-date-picker__panel .t-pagination-mini .t-pagination-mini__current {
   display: none;
   display: none;
 }
 }
 .t-dialog__ctx .t-dialog__position.t-dialog--top {
 .t-dialog__ctx .t-dialog__position.t-dialog--top {
-  // padding: 0;
-  // align-items: center;
+  padding: 0;
+  align-items: center;
 }
 }
 .t-table__empty-row > td {
 .t-table__empty-row > td {
   padding: 0 !important;
   padding: 0 !important;

+ 38 - 0
src/views/login/forget-pwd.vue

@@ -0,0 +1,38 @@
+<template>
+  <div class="forget-pwd">
+    <t-form
+      v-show="!forgetStatus"
+      ref="form"
+      :data="formData"
+      :label-width="0"
+      class="login-form"
+      :rules="rules"
+    >
+      <t-form-item name="tel">
+        <t-input
+          v-model="formData.tel"
+          clearable
+          placeholder="请输入你的手机号"
+          size="large"
+        >
+          <template #prefix-icon>
+            <CallIcon />
+          </template>
+        </t-input>
+      </t-form-item>
+    </t-form>
+  </div>
+</template>
+
+<script setup name="ForgetPwd">
+import { reactive } from 'vue';
+import { CallIcon } from 'tdesign-icons-vue-next';
+
+const formData = reactive({
+  tel: '',
+  loginName: 'sysadmin',
+  password: '123456',
+});
+</script>
+
+<style></style>

+ 101 - 14
src/views/login/index.vue

@@ -1,7 +1,31 @@
 <template>
 <template>
   <div class="login flex justify-center items-center h-full">
   <div class="login flex justify-center items-center h-full">
+    <img class="logo" src="../../assets/imgs/login_logo.png" />
+    <div class="login-bg flex justify-center items-center">
+      <div class="login-bg-inner-box text-center">
+        <img src="../../assets/imgs/login_bg_inner.png" />
+        <div class="title1">欢 迎 使 用</div>
+        <div class="title2">项目质量控制管理平台</div>
+      </div>
+    </div>
     <div class="login-box">
     <div class="login-box">
+      <div class="title1 flex justify-between items-center">
+        <span>{{ forgetStatus ? '忘记密码' : '输入信息' }}</span>
+        <t-button
+          variant="outline"
+          theme="primary"
+          v-if="forgetStatus"
+          @click="forgetStatus = false"
+        >
+          <template #icon><RollbackIcon /></template>
+          返回登录
+        </t-button>
+      </div>
+      <div class="title2">{{
+        `请输入${forgetStatus ? '新的' : ''}账号与密码`
+      }}</div>
       <t-form
       <t-form
+        v-show="!forgetStatus"
         ref="form"
         ref="form"
         :data="formData"
         :data="formData"
         :label-width="0"
         :label-width="0"
@@ -12,7 +36,7 @@
           <t-input
           <t-input
             v-model="formData.loginName"
             v-model="formData.loginName"
             clearable
             clearable
-            placeholder="账号"
+            placeholder="请输入账号"
             size="large"
             size="large"
           >
           >
             <template #prefix-icon>
             <template #prefix-icon>
@@ -26,7 +50,7 @@
             v-model="formData.password"
             v-model="formData.password"
             type="password"
             type="password"
             clearable
             clearable
-            placeholder="密码"
+            placeholder="请输入密码"
             size="large"
             size="large"
           >
           >
             <template #prefix-icon>
             <template #prefix-icon>
@@ -34,15 +58,18 @@
             </template>
             </template>
           </t-input>
           </t-input>
         </t-form-item>
         </t-form-item>
+        <t-link theme="primary" class="m-t-20px" @click="forgetStatus = true"
+          >忘记密码</t-link
+        >
+        <t-button
+          block
+          class="m-t-30px"
+          @click="loginHandle"
+          size="large"
+          theme="primary"
+          >登 录</t-button
+        >
       </t-form>
       </t-form>
-      <t-button
-        block
-        class="m-t-30px"
-        @click="loginHandle"
-        size="large"
-        theme="success"
-        >登 录</t-button
-      >
     </div>
     </div>
   </div>
   </div>
   <!-- <button @click="loginHandle" class="m-t-10px">登录!</button> -->
   <!-- <button @click="loginHandle" class="m-t-10px">登录!</button> -->
@@ -50,12 +77,13 @@
 
 
 <script setup name="Login">
 <script setup name="Login">
 import { ref, reactive, computed, watch } from 'vue';
 import { ref, reactive, computed, watch } from 'vue';
-import { DesktopIcon, LockOnIcon } from 'tdesign-icons-vue-next';
+import { DesktopIcon, LockOnIcon, RollbackIcon } from 'tdesign-icons-vue-next';
 import { useRoute, useRouter } from 'vue-router';
 import { useRoute, useRouter } from 'vue-router';
 import { useUserStore } from '@/store';
 import { useUserStore } from '@/store';
 import { MessagePlugin } from 'tdesign-vue-next';
 import { MessagePlugin } from 'tdesign-vue-next';
 import { getBase64 } from '@/utils/crypto';
 import { getBase64 } from '@/utils/crypto';
 
 
+const forgetStatus = ref(false);
 const form = ref(null);
 const form = ref(null);
 
 
 const route = useRoute();
 const route = useRoute();
@@ -99,13 +127,72 @@ const loginHandle = () => {
 </script>
 </script>
 <style lang="less" scoped>
 <style lang="less" scoped>
 .login {
 .login {
-  background: rgba(26, 188, 156, 1);
+  background: linear-gradient(180deg, #f4f9ff 0%, #d7eaff 100%);
+  position: relative;
+  min-height: 550px;
+
+  .logo {
+    width: 180px;
+    z-index: 100;
+    top: 48px;
+    left: 48px;
+    position: absolute;
+  }
+  .login-bg {
+    position: absolute;
+    top: 80px;
+    bottom: 80px;
+    left: 0;
+    width: 70%;
+    z-index: 99;
+    background: url(../../assets//imgs/login_bg_grid.png) center center
+      no-repeat;
+    background-size: contain;
+    .login-bg-inner-box {
+      img {
+        height: 260px;
+        margin-bottom: 20px;
+      }
+      .title1 {
+        font-size: 32px;
+        color: #262626;
+        margin-bottom: 15px;
+        font-weight: bold;
+        line-height: 40px;
+      }
+      .title2 {
+        font-size: 20px;
+        color: #8c8c8c;
+        line-height: 28px;
+      }
+    }
+  }
   .login-box {
   .login-box {
     width: 350px;
     width: 350px;
+    height: 360px;
     background: #fff;
     background: #fff;
-    box-shadow: rgba(0, 0, 0, 0.35) 0px 0px 10px;
-    border-radius: 10px;
+    box-shadow: 0px 2px 12px 0px rgba(0, 0, 0, 0.08);
+    border-radius: 8px;
+    border: 1px solid #e5e6eb;
     padding: 24px;
     padding: 24px;
+    position: absolute;
+    left: 60%;
+    z-index: 101;
+    .title1 {
+      span {
+        font-size: 20px;
+        font-weight: bold;
+        color: #262626;
+        line-height: 32px;
+      }
+    }
+    .title2 {
+      font-size: 14px;
+      color: #8c8c8c;
+      line-height: 24px;
+      margin-top: 4px;
+      margin-bottom: 28px;
+    }
   }
   }
 }
 }
 </style>
 </style>

+ 86 - 11
src/views/user/auth-manage/role-manage/add-role-dialog.vue

@@ -2,22 +2,25 @@
   <my-dialog
   <my-dialog
     :visible="visible"
     :visible="visible"
     @close="emit('update:visible', false)"
     @close="emit('update:visible', false)"
-    :header="`${isEdit ? '修改' : '新增'}用户`"
+    :header="`${isEdit ? '修改' : '新增'}角色`"
     :width="800"
     :width="800"
     :closeOnOverlayClick="false"
     :closeOnOverlayClick="false"
   >
   >
-    <t-form ref="formRef" :model="formData" labelWidth="120px">
-      <t-form-item label="角色名称">
-        <t-input v-model="formData.a"></t-input>
+    <t-form ref="formRef" :data="formData" :rules="rules" labelWidth="120px">
+      <t-form-item label="角色名称" name="name">
+        <t-input v-model="formData.name"></t-input>
       </t-form-item>
       </t-form-item>
       <t-form-item label="绑定权限">
       <t-form-item label="绑定权限">
         <!-- <t-tree-select v-model="formData.d" :data="treeData" multiple /> -->
         <!-- <t-tree-select v-model="formData.d" :data="treeData" multiple /> -->
 
 
         <t-tree
         <t-tree
+          v-model="treeChecked"
+          ref="treeRef"
           :data="treeData"
           :data="treeData"
           hover
           hover
           expand-all
           expand-all
           :checkable="true"
           :checkable="true"
+          value-mode="all"
           @change="onTreeChange"
           @change="onTreeChange"
           :keys="{ value: 'id', label: 'name' }"
           :keys="{ value: 'id', label: 'name' }"
         ></t-tree>
         ></t-tree>
@@ -34,7 +37,7 @@
 <script setup name="AddRoleDialog">
 <script setup name="AddRoleDialog">
 import useClearDialog from '@/hooks/useClearDialog';
 import useClearDialog from '@/hooks/useClearDialog';
 import { ref, watch } from 'vue';
 import { ref, watch } from 'vue';
-import { getAllMenuResource } from '@/api/user';
+import { getAllMenuResource, addRole, getRoleDetail } from '@/api/user';
 import { useRequest } from 'vue-request';
 import { useRequest } from 'vue-request';
 const props = defineProps({
 const props = defineProps({
   visible: Boolean,
   visible: Boolean,
@@ -42,25 +45,97 @@ const props = defineProps({
 });
 });
 const emit = defineEmits(['update:visible']);
 const emit = defineEmits(['update:visible']);
 const formRef = ref(null);
 const formRef = ref(null);
+const treeRef = ref(null);
+const curRoleBindIds = ref([]);
+const rules = {
+  name: [
+    {
+      required: true,
+      message: '请选择角色',
+    },
+  ],
+};
+const initChecked = (res) => {
+  let privilegeIds = curRoleBindIds.value;
+  let leafNodes = [];
+  privilegeIds.forEach((id) => {
+    if (!treeFlatArr.value.find((item) => item.parentId == id)) {
+      leafNodes.push(id);
+    }
+  });
+  console.log('leafNodes:', leafNodes);
+  treeChecked.value = leafNodes.map((item) => item.toString());
+};
 const getDetail = async () => {
 const getDetail = async () => {
   //编辑状态下获取回显数据的接口请求业务,如果curRow里的字段够用,就直接把curRow里的字段赋值给formData
   //编辑状态下获取回显数据的接口请求业务,如果curRow里的字段够用,就直接把curRow里的字段赋值给formData
-  alert('获取详情中...');
+  formData.name = props.curRow.name;
+  getRoleDetail({ roleId: props.curRow.id }).then((res) => {
+    console.log('rrr', res);
+    curRoleBindIds.value = JSON.parse(res.privilegeIds || '[]');
+    if (treeFlatArr.value.length) {
+      initChecked();
+    }
+  });
 };
 };
 const { formData, isEdit } = useClearDialog(
 const { formData, isEdit } = useClearDialog(
   {
   {
-    a: '',
-    b: '',
+    name: '',
   },
   },
   props,
   props,
+  formRef,
   getDetail
   getDetail
 );
 );
 
 
 const { data: treeData } = useRequest(getAllMenuResource, {
 const { data: treeData } = useRequest(getAllMenuResource, {
   manual: false,
   manual: false,
 });
 });
-
+let treeFlatArr = ref([]);
+watch(treeData, () => {
+  treeFlatArr.value = treeToArr(treeData.value || []);
+  initChecked();
+});
+const treeToArr = (tree, arr = []) => {
+  for (let i = 0; i < tree.length; i++) {
+    arr.push(tree[i]);
+    if (tree[i].children && tree[i].children.length) {
+      treeToArr(tree[i].children, arr);
+    }
+  }
+  return arr;
+};
+const findParentIdById = (id, arr = []) => {
+  let item = treeFlatArr.value.find((v) => v.id == id);
+  if (item.parentId != '-1') {
+    let find = treeFlatArr.value.find((v) => v.id == item.parentId);
+    arr.push(find.id);
+    findParentIdById(find.id, arr);
+  }
+  return arr;
+};
+const treeChecked = ref([]);
+const checkedResult = ref([]);
 const onTreeChange = (checked, context) => {
 const onTreeChange = (checked, context) => {
-  console.info('onChange:', checked, context);
+  let allParentIds = [];
+  checked.forEach((id) => {
+    let parentIds = findParentIdById(id);
+    allParentIds.push(...parentIds);
+  });
+  checkedResult.value = Array.from(new Set([...allParentIds, ...checked]));
+};
+const addHandler = () => {
+  addRole({
+    name: formData.name,
+    privilegeIds: checkedResult.value,
+    id: props.curRow?.id || undefined,
+  }).then(() => {
+    emit('success');
+  });
+};
+const save = () => {
+  formRef.value.validate().then(async (result) => {
+    if (result === true) {
+      addHandler();
+    }
+  });
 };
 };
-const save = () => {};
 </script>
 </script>

+ 27 - 7
src/views/user/auth-manage/role-manage/index.vue

@@ -30,16 +30,17 @@
     <AddRoleDialog
     <AddRoleDialog
       v-model:visible="showAddRoleDialog"
       v-model:visible="showAddRoleDialog"
       :curRow="curRow"
       :curRow="curRow"
+      @success="addSuccess"
     ></AddRoleDialog>
     ></AddRoleDialog>
   </div>
   </div>
 </template>
 </template>
 
 
 <script setup name="User" lang="jsx">
 <script setup name="User" lang="jsx">
 import { reactive, ref } from 'vue';
 import { reactive, ref } from 'vue';
-import { useRequest } from 'vue-request';
+import { DialogPlugin, MessagePlugin } from 'tdesign-vue-next';
 import useFetchTable from '@/hooks/useFetchTable';
 import useFetchTable from '@/hooks/useFetchTable';
 import AddRoleDialog from './add-role-dialog.vue';
 import AddRoleDialog from './add-role-dialog.vue';
-import { getRoleList } from '@/api/user';
+import { getRoleList, deleteRole } from '@/api/user';
 const showAddRoleDialog = ref(false);
 const showAddRoleDialog = ref(false);
 const curRow = ref(null);
 const curRow = ref(null);
 const columns = [
 const columns = [
@@ -48,7 +49,7 @@ const columns = [
     title: '操作',
     title: '操作',
     colKey: 'operate',
     colKey: 'operate',
     fixed: 'right',
     fixed: 'right',
-    width: 150,
+    width: 200,
     cell: (h, { row }) => {
     cell: (h, { row }) => {
       return (
       return (
         <div class="table-operations">
         <div class="table-operations">
@@ -58,16 +59,17 @@ const columns = [
             onClick={(e) => {
             onClick={(e) => {
               e.stopPropagation();
               e.stopPropagation();
               curRow.value = row;
               curRow.value = row;
-              showAddUserDialog.value = true;
+              showAddRoleDialog.value = true;
             }}
             }}
           >
           >
             修改
             修改
           </t-link>
           </t-link>
           <t-link
           <t-link
-            theme="primary"
+            theme="danger"
             hover="color"
             hover="color"
             onClick={(e) => {
             onClick={(e) => {
               e.stopPropagation();
               e.stopPropagation();
+              handleDelete(row);
             }}
             }}
           >
           >
             删除
             删除
@@ -77,7 +79,21 @@ const columns = [
     },
     },
   },
   },
 ];
 ];
-
+const handleDelete = (row) => {
+  const confirmDia = DialogPlugin({
+    header: '操作提示',
+    body: `确定要删除角色 ${row.name} 吗`,
+    confirmBtn: '确定',
+    cancelBtn: '取消',
+    onConfirm: async () => {
+      confirmDia.hide();
+      const res = await deleteRole({ roleId: row.id }).catch(() => {});
+      if (!res) return;
+      MessagePlugin.success('删除成功');
+      search();
+    },
+  });
+};
 const {
 const {
   loading: tableLoading,
   loading: tableLoading,
   pagination,
   pagination,
@@ -87,7 +103,11 @@ const {
   search,
   search,
 } = useFetchTable(getRoleList);
 } = useFetchTable(getRoleList);
 
 
-const refresh = async () => {};
+const addSuccess = () => {
+  showAddRoleDialog.value = false;
+  MessagePlugin.success('操作成功');
+  search();
+};
 </script>
 </script>
 
 
 <style></style>
 <style></style>

+ 12 - 1
vite.config.js

@@ -13,6 +13,7 @@ import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfil
 import { NodeModulesPolyfillPlugin } from '@esbuild-plugins/node-modules-polyfill';
 import { NodeModulesPolyfillPlugin } from '@esbuild-plugins/node-modules-polyfill';
 // You don't need to add this to deps, it's included by @esbuild-plugins/node-modules-polyfill
 // You don't need to add this to deps, it's included by @esbuild-plugins/node-modules-polyfill
 import rollupNodePolyFill from 'rollup-plugin-node-polyfills';
 import rollupNodePolyFill from 'rollup-plugin-node-polyfills';
+import { resolve } from 'path';
 
 
 export default defineConfig((configEnv) => {
 export default defineConfig((configEnv) => {
   const viteEnv = loadEnv(configEnv.mode, process.cwd());
   const viteEnv = loadEnv(configEnv.mode, process.cwd());
@@ -43,7 +44,17 @@ export default defineConfig((configEnv) => {
       },
       },
       extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
       extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
     },
     },
-
+    css: {
+      preprocessorOptions: {
+        less: {
+          javascriptEnabled: true,
+          additionalData: `@import "${resolve(
+            __dirname,
+            'src/style/color.less'
+          )}";`,
+        },
+      },
+    },
     define: { ...viteDefine, ...processEnvValues },
     define: { ...viteDefine, ...processEnvValues },
     plugins: [
     plugins: [
       ...setupVitePlugins(viteEnv),
       ...setupVitePlugins(viteEnv),