zhangjie 1 жил өмнө
parent
commit
d4913bb0d2

+ 3 - 1
package.json

@@ -28,6 +28,7 @@
     "mitt": "^3.0.0",
     "moment": "^2.29.1",
     "pinia": "~2.0.11",
+    "pinia-plugin-persistedstate": "^3.2.1",
     "qs": "^6.10.3",
     "tailwindcss": "^3.0.19",
     "ua-parser-js": "^1.0.2",
@@ -57,7 +58,8 @@
     "typescript": "^4.5.5",
     "unplugin-vue-components": "^0.26.0",
     "vite": "^2.8.0",
+    "vite-plugin-svg-icons": "^2.0.1",
     "vue-eslint-parser": "^8.2.0",
     "vue-tsc": "^0.31.2"
   }
-}
+}

+ 12 - 0
src/assets/svgs/add.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon-新增</title>
+    <g id="管理员" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="02.01-项目列表" transform="translate(-40, -171)">
+            <g id="icon-新增" transform="translate(40, 171)">
+                <rect id="add-circle-(Background)" opacity="0" x="0" y="0" width="16" height="16"></rect>
+                <path d="M4.5,8.5 L4.5,7.5 L7.5,7.5 L7.5,4.5 L8.5,4.5 L8.5,7.5 L11.5,7.5 L11.5,8.5 L8.5,8.5 L8.5,11.5 L7.5,11.5 L7.5,8.5 L4.5,8.5 Z M15,8 C15,4.13400674 11.8659935,1 8,1 C4.13400674,1 1,4.13400674 1,8 C1,11.8659935 4.13400674,15 8,15 C11.8659935,15 15,11.8659935 15,8 Z" id="add-circle" fill="#595959"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 12 - 0
src/assets/svgs/auth-manage.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon-授权管理-off</title>
+    <g id="超管" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="05.01-学校管理" transform="translate(-190, -20)">
+            <g id="icon-授权管理-off" transform="translate(190, 20)">
+                <rect id="secured-(Background)" opacity="0" x="0" y="0" width="16" height="16"></rect>
+                <path d="M10.9643965,6.3537941 L10.2572827,5.64669418 L7.38939476,8.51463604 L5.74265432,6.86789799 L5.03554821,7.57500553 L7.38940239,9.9288559 L10.9643965,6.3537941 Z M2.5,9.00004625 C2.5,10.4164762 3.24315226,11.7290487 4.45771623,12.4578152 L7.99979258,14.5831442 L11.5422773,12.457757 C12.7568684,11.7290373 13.5000772,10.4164734 13.5000772,9.00005007 L13.5000772,2 L2.5,2 L2.5,9.00004625 Z M3.50002468,3 L12.5000734,3 L12.5000477,9.00004625 C12.5000477,10.0652037 11.9411755,11.0522547 11.0278006,11.6002531 L7.99981546,13.416955 L4.97222662,11.6003313 C4.05885947,11.05229 3.5,10.0652189 3.5,9.00005007 L3.50002468,3 Z" id="secured" fill="#262626"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 12 - 0
src/assets/svgs/delete.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon-批量删除</title>
+    <g id="管理员" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="02.01-项目列表" transform="translate(-116, -171)">
+            <g id="icon-批量删除" transform="translate(116, 171)">
+                <rect id="delete-(Background)" opacity="0" x="0" y="0" width="16" height="16"></rect>
+                <path d="M6,6 L7,6 L7,12 L6,12 L6,6 Z M9,12 L10,12 L10,6 L9,6 L9,12 Z M14,3 L14,4 L13,4 L13,14 C13,14.5522852 12.5522842,15 12,15 L4,15 C3.44771504,15 3,14.5522842 3,14 L3,4 L2,4 L2,3 L5.5,3 L5.5,1.80000073 C5.5,1.35817304 5.85817218,1 6.30000019,1 L9.69999981,1 C10.1418276,1 10.5,1.35817215 10.5,1.80000013 L10.5,3 L14,3 Z M6.5,2 L9.5,2 L9.5,3 L6.5,3 L6.5,2 Z M4,14 L12,14 L12,4 L4,4 L4,14 Z" id="delete" fill="#595959"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 12 - 0
src/assets/svgs/logout.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon-退出</title>
+    <g id="管理员" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="02.01-项目列表" transform="translate(-1400, -20)">
+            <g id="icon-退出" transform="translate(1400, 20)">
+                <rect id="logout-(Background)" opacity="0" x="0" y="0" width="16" height="16"></rect>
+                <path d="M9.49971536,2 C9.77584611,2 9.99969861,2.22386 9.99969861,2.5 L9.99969861,5 L8.9997321,5 L8.9997321,3 L1.99996651,3 L1.99996651,13 L8.9997321,13 L8.9997321,11 L9.99969861,11 L9.99969861,13.5 C9.99969861,13.7761 9.77584611,14 9.49971536,14 L1.49998326,14 C1.2238525,14 1,13.7761 1,13.5 L1,2.5 C1,2.22386 1.2238525,2 1.49998326,2 Z M11.9386344,4.54712 L15.0379875,7.64655874 C15.2332908,7.84182496 15.2332908,8.15841504 15.0379875,8.35368126 L11.9386344,11.45312 L11.2315222,10.7459975 L13.4773607,8.50013593 L5.99977257,8.50011593 L5.99977257,7.50008407 L13.4773607,7.50010407 L11.2315222,5.25425253 L11.9386344,4.54712 Z" id="logout" fill="#595959"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 12 - 0
src/assets/svgs/password.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon-密码</title>
+    <g id="管理员" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="02.01-项目列表" transform="translate(-1360, -20)">
+            <g id="icon-密码" transform="translate(1360, 20)">
+                <rect id="lock-on-(Background)" opacity="0" x="0" y="0" width="16" height="16"></rect>
+                <path d="M7.50263262,8.01738691 L7.50263262,12.0173869 L8.50263262,12.0173869 L8.50263262,8.01738691 L7.50263262,8.01738691 Z M4.5,5.99998188 L3,5.99998188 C2.72385764,5.99998188 2.5,6.22383976 2.5,6.49998188 L2.5,13.4999838 C2.5,13.7761259 2.72385782,13.9999838 3,13.9999838 L13,13.9999838 C13.2761421,13.9999838 13.5,13.7761259 13.5,13.4999838 L13.5,6.49998188 C13.5,6.22383976 13.2761421,5.99998188 13,5.99998188 L11.5,5.99998188 L11.5,4.99084473 C11.5,3.05784559 9.93299675,1.49084473 8,1.49084473 C6.06700349,1.49084473 4.5,3.05784559 4.5,4.99084473 L4.5,5.99998188 Z M10.5,4.99084473 C10.5,3.61013031 9.38071203,2.49084473 8,2.49084473 C6.61928844,2.49084473 5.5,3.61013031 5.5,4.99084473 L5.5,5.99998188 L10.5,5.99998188 L10.5,4.99084473 Z M12.5,6.99998188 L12.5,12.9999838 L3.5,12.9999838 L3.5,6.99998188 L12.5,6.99998188 Z" id="lock-on" fill="#595959"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 14 - 0
src/assets/svgs/project-list.svg

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon-项目列表-off</title>
+    <g id="管理员" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="03.01-科目管理" transform="translate(-190, -20)">
+            <g id="编组-3" transform="translate(174, 12)">
+                <g id="icon-项目列表-off" transform="translate(16, 8)">
+                    <rect id="wallet-(Background)" opacity="0" x="0" y="0" width="16" height="16"></rect>
+                    <path d="M13.5,4.5 C14.0522852,4.5 14.5,4.94771528 14.5,5.5 L14.5,12 C14.5,12.5522852 14.0522842,13 13.5,13 L2.5,13 C1.94771522,13 1.5,12.5522852 1.5,12 L1.5,3 C1.5,2.44771498 1.94771528,2 2.5,2 L10.5,2 C11.0522842,2 11.5,2.44771498 11.5,3 L11.5,4.5 L13.5,4.5 Z M2.5,3 L2.5,4.5 L10.5,4.5 L10.5,3 L2.5,3 Z M2.5,5.5 L2.5,12 L13.5,12 L13.5,5.5 L2.5,5.5 Z" id="wallet" fill="#262626"></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 14 - 0
src/assets/svgs/project-manage.svg

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon-科目管理-off</title>
+    <g id="管理员" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="02.01-项目列表" transform="translate(-310, -20)">
+            <g id="编组-3" transform="translate(294, 12)">
+                <g id="icon-科目管理-off" transform="translate(16, 8)">
+                    <rect id="view-module-(Background)" opacity="0" x="0" y="0" width="16" height="16"></rect>
+                    <path d="M11,7.5 L11,8.5 L4,8.5 L4,7.5 L11,7.5 Z M4,10 L4,11 L10,11 L10,10 L4,10 Z M3,2 L13,2 C13.5522852,2 14,2.44771528 14,3 L14,13 C14,13.5522852 13.5522842,14 13,14 L3,14 C2.44771504,14 2,13.5522842 2,13 L2,3 C2,2.44771504 2.44771528,2 3,2 Z M13,3 L13,5 L3,5 L3,3 L13,3 Z M3,13 L13,13 L13,6 L3,6 L3,13 Z" id="view-module" fill="#262626"></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 11 - 0
src/assets/svgs/school-manage.svg

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon-学校管理-on</title>
+    <g id="超管" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="05.01-学校管理" transform="translate(-310, -20)">
+            <g id="icon-学校管理-on" transform="translate(310, 20)">
+                <path d="M9.08333333,1.5 C9.68164181,1.5 10.1666667,1.98502485 10.1666667,2.58333333 L10.1666667,5.83333333 L13.4166667,5.83333333 C14.0149751,5.83333333 14.5,6.31835819 14.5,6.91666667 L14.5,13.4166667 C14.5,14.0149751 14.0149751,14.5 13.4166667,14.5 L2.58333333,14.5 C1.98502485,14.5 1.5,14.0149751 1.5,13.4166667 L1.5,2.58333333 C1.5,1.98502485 1.98502485,1.5 2.58333333,1.5 L9.08333333,1.5 Z M9.08333333,2.58333333 L2.58333333,2.58333333 L2.58333333,13.4166667 L9.08333333,13.4166667 L9.08333333,2.58333333 Z M13.4166667,6.91666667 L10.1666667,6.91666667 L10.1666667,13.4166667 L13.4166667,13.4166667 L13.4166667,6.91666667 Z M7.45833333,10.1666667 L7.45833333,11.25 L4.20833333,11.25 L4.20833333,10.1666667 L7.45833333,10.1666667 Z M7.45833333,7.45833333 L7.45833333,8.54166667 L4.20833333,8.54166667 L4.20833333,7.45833333 L7.45833333,7.45833333 Z M7.45833333,4.75 L7.45833333,5.83333333 L4.20833333,5.83333333 L4.20833333,4.75 L7.45833333,4.75 Z" id="形状结合" fill="#262626" fill-rule="nonzero"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 12 - 0
src/assets/svgs/user-manage.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon-用户管理-off</title>
+    <g id="超管" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="05.01-学校管理" transform="translate(-430, -20)">
+            <g id="icon-用户管理-off" transform="translate(430, 20)">
+                <polygon id="Rectangle-4117" opacity="0" transform="translate(8, 8) rotate(0) translate(-8, -8)" points="2.79752862e-06 2.79752859e-06 16.0000085 2.79752859e-06 16.0000085 16.0000085 2.79752862e-06 16.0000085"></polygon>
+                <path d="M8.00000613,8.50000054 C6.06701101,8.50000054 4.50000613,6.93299923 4.50000613,5.00000054 C4.50000613,3.06700411 6.06701101,1.50000054 8.00000613,1.50000054 C9.9330059,1.50000054 11.5000061,3.06700411 11.5000061,5.00000054 C11.5000061,6.93299923 9.9330059,8.50000054 8.00000613,8.50000054 Z M8.00000613,7.50000054 C6.61929545,7.50000054 5.50000613,6.38071384 5.50000613,5.00000054 C5.50000613,3.61928902 6.61929545,2.50000054 8.00000613,2.50000054 C9.38072051,2.50000054 10.5000061,3.61928902 10.5000061,5.00000054 C10.5000061,6.38071431 9.38072051,7.50000054 8.00000613,7.50000054 Z M14.5000061,11.7246326 L14.5000061,14.0000005 C14.5000061,14.2761472 14.2761528,14.5000005 14.0000061,14.5000005 L2.00000613,14.5000005 C1.72386513,14.5000005 1.50000613,14.2761472 1.50000613,14.0000005 L1.50000613,11.7246326 C1.50000613,11.3546603 1.70302213,11.01223 2.03687054,10.8527785 C3.84941006,9.98707938 5.86651665,9.50000054 8.00000613,9.50000054 C10.1335007,9.50000054 12.1506075,9.98707938 13.9631473,10.8527785 C14.2969954,11.01223 14.5000061,11.3546603 14.5000061,11.7246326 Z M13.5000061,11.7398352 L13.5000061,13.5000005 L2.50000613,13.5000005 L2.50000613,11.7398352 C4.1743521,10.9457313 6.03341163,10.5000005 8.00000613,10.5000005 C9.96660576,10.5000005 11.8256658,10.9457313 13.5000061,11.7398352 Z" id="Union" fill="#262626"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 12 - 0
src/assets/svgs/user.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>icon-用户</title>
+    <g id="管理员" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="02.01-项目列表" transform="translate(-1272, -20)">
+            <g id="icon-用户" transform="translate(1272, 20)">
+                <polygon id="Rectangle-4117" opacity="0" transform="translate(8, 8) rotate(0) translate(-8, -8)" points="2.79752671e-06 2.79752959e-06 15.9999972 2.79752959e-06 15.9999972 15.9999972 2.79752671e-06 15.9999972"></polygon>
+                <path d="M8.00000507,8.49999948 C6.06700672,8.49999948 4.50000507,6.93299433 4.50000507,4.99999948 C4.50000507,3.06700194 6.06700672,1.49999948 8.00000507,1.49999948 C9.93299887,1.49999948 11.5000051,3.06700194 11.5000051,4.99999948 C11.5000051,6.93299433 9.93299887,8.49999948 8.00000507,8.49999948 Z M14.5000051,11.7246243 L14.5000051,13.9999995 C14.5000051,14.2761371 14.2761427,14.4999995 14.0000051,14.4999995 L2.00000507,14.4999995 C1.72386391,14.4999995 1.50000507,14.2761371 1.50000507,13.9999995 L1.50000507,11.7246243 C1.50000507,11.3546522 1.70302092,11.0122223 2.0368691,10.8527708 C3.84940733,9.98707231 5.8665125,9.49999948 8.00000507,9.49999948 C10.1334936,9.49999948 12.1505989,9.98707231 13.9631374,10.8527708 C14.2969853,11.0122223 14.5000051,11.3546522 14.5000051,11.7246243 Z" id="Union" fill="#BFBFBF"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 81 - 151
src/components/Layout.vue

@@ -1,181 +1,111 @@
 <template>
-  <div class="tw-h-screen tw-flex">
-    <div class="tw-bg-white">
-      <div class="tw-py-2 tw-flex tw-justify-center">
-        <img class="qm-logo-img" />
+  <div class="home">
+    <div class="home-header">
+      <div class="home-menu">
+        <div class="home-title">
+          <h1>研究生成绩分析</h1>
+        </div>
+        <div
+          v-for="menu in menus"
+          :key="menu.url"
+          :class="['home-menu-item', { 'is-active': curMenu == menu.url }]"
+          @click="toMenu(menu)"
+        >
+          <svg-icon :name="menu.icon"></svg-icon>
+          <span>{{ menu.name }}</span>
+        </div>
+      </div>
+      <div class="home-action">
+        <div class="home-action-item">
+          <svg-icon name="user" color="#BFBFBF"></svg-icon>
+          <span>{{ store.userInfo.displayName }}</span>
+        </div>
+        <a-button type="text" @click="toModifyPwd">
+          <template #icon>
+            <svg-icon name="password"></svg-icon>
+          </template>
+        </a-button>
+        <a-button type="text" @click="doLogout">
+          <template #icon>
+            <svg-icon name="logout"></svg-icon>
+          </template>
+        </a-button>
       </div>
-      <a-menu style="width: 240px" mode="inline" :openKeys="['sub1', 'sub2']">
-        <a-sub-menu key="sub1">
-          <template #icon> <span class="basic-icon"></span> </template>
-          <template #title>基础管理</template>
-          <a-menu-item v-if="store.isSuperAdmin" key="11">
-            <router-link activeClass="active-route" to="/basic/rootOrg">
-              学校管理
-            </router-link>
-          </a-menu-item>
-          <a-menu-item v-if="store.isSuperAdmin" key="16">
-            <router-link activeClass="active-route" to="/basic/auth">
-              授权管理
-            </router-link>
-          </a-menu-item>
-          <!-- <a-menu-item v-if="store.isSuperAdmin" key="12">
-            <router-link activeClass="active-route" to="/basic/subOrg">
-              机构管理
-            </router-link>
-          </a-menu-item> -->
-          <!-- <a-menu-item v-if="store.isSuperAdmin" key="13">
-            <router-link activeClass="active-route" to="/basic/role">
-              角色管理
-            </router-link>
-          </a-menu-item> -->
-          <a-menu-item v-if="!store.isCourseAdmin" key="14">
-            <router-link activeClass="active-route" to="/basic/user">
-              用户管理
-            </router-link>
-          </a-menu-item>
-          <a-menu-item v-if="!store.isSuperAdmin" key="15">
-            <router-link activeClass="active-route" to="/basic/course">
-              科目管理
-            </router-link>
-          </a-menu-item>
-        </a-sub-menu>
-        <a-sub-menu v-if="!store.isSuperAdmin" key="sub2">
-          <template #icon> <span class="project-icon"></span> </template>
-          <template #title>项目管理</template>
-          <a-menu-item key="21">
-            <router-link
-              activeClass="active-route"
-              to="/project/projectManagement"
-            >
-              项目列表
-            </router-link>
-          </a-menu-item>
-          <a-menu-item key="22">
-            <router-link
-              activeClass="active-route"
-              to="/project/projectCompareManagement"
-            >
-              关联分析
-            </router-link>
-          </a-menu-item>
-        </a-sub-menu>
-      </a-menu>
     </div>
-    <div
-      class="tw-h-screen"
-      style="width: calc(100% - 240px); overflow: scroll; min-width: 960px"
-    >
-      <div
-        class="tw-bg-white tw-flex tw-justify-between tw-items-center"
-        style="border-left: 1px solid var(--app-main-bg-color); height: 56px"
-      >
-        <div>
-          <span class="current-location-icon"></span>
-          <span style="color: var(--app-secondary-text-color)">
-            当前所在位置:<span class="location-teleport">{{
-              store.currentLocation
-            }}</span></span
+    <div class="home-body">
+      <div v-if="store.currentLocation" class="home-breadcrumb">
+        <span class="breadcrumb-tips">
+          <HomeOutlined />
+          <span style="margin-left: 7px"
+            >当前所在位置:{{ store.currentLocation }}</span
           >
-        </div>
-        <div class="tw-flex tw-items-center tw-cursor-pointer tw-gap-2 tw-mr-2">
-          <div class="tw-cursor-pointer" @click="visible = true">
-            {{ store.userInfo.displayName }}
-          </div>
-
-          <div class="logout-icon" @click="doLogout"></div>
-        </div>
+        </span>
       </div>
-      <router-view class="tw-m-8"></router-view>
+      <router-view></router-view>
     </div>
 
-    <a-modal
-      v-model:visible="visible"
-      title="修改密码"
-      okText="确定"
-      cancelText="取消"
-      @ok="handleOk"
-    >
-      <a-form :labelCol="{ span: 6 }">
-        <a-form-item label="新密码">
-          <a-input-password v-model:value="newPassword"></a-input-password>
-        </a-form-item>
-        <a-form-item label="重复输入新密码">
-          <a-input-password v-model:value="newPasswordCopy"></a-input-password>
-        </a-form-item>
-      </a-form>
-    </a-modal>
+    <!-- ModifyPwd -->
+    <ModifyPwd ref="modifyPwdRef" />
   </div>
 </template>
 
 <script setup lang="ts">
+import { HomeOutlined } from "@ant-design/icons-vue";
 import { logout } from "@/api/loginPage";
-import { changePassword } from "@/api/userManagementPage";
 import { routeLogout } from "@/router";
 import { useMainStore } from "@/store";
 import { message } from "ant-design-vue";
+import useAuth from "@/hooks/auth";
+import { menuRelative } from "@/constants/menu";
+import type { MenuItem } from "@/constants/menu";
+import { useRoute, useRouter } from "vue-router";
+import { ref, watch } from "vue";
+import ModifyPwd from "../features/userManagement/ModifyPwd.vue";
 
+const route = useRoute();
+const router = useRouter();
 const store = useMainStore();
 
-async function doLogout() {
-  await logout().then(() =>
-    routeLogout({ cause: "主动退出", redirectTo: "/" })
-  );
-}
+const { menus } = useAuth(store.userInfo.role);
+let curMenu = ref();
+type MenuKey = keyof typeof menuRelative;
 
-let visible = $ref(false);
-let newPassword = $ref("");
-let newPasswordCopy = $ref("");
+watch(
+  () => route.name,
+  (val) => {
+    console.log(val);
 
-async function handleOk() {
-  if (newPassword.length < 6) {
-    void message.error({ content: "密码长度必须至少大于6位" });
-    return;
+    if (!val) return;
+    const routeName = val.toString();
+    const keys: MenuKey[] = Object.keys(menuRelative) as MenuKey[];
+    const rname = keys.find((k) => menuRelative[k].includes(routeName));
+    curMenu.value = rname ?? routeName;
+  },
+  {
+    immediate: true,
   }
-  if (newPassword !== newPasswordCopy) {
-    void message.error({ content: "两次输入的密码不一致" });
-    return;
-  }
-  await changePassword(newPassword);
-  void message.success("密码修改成功");
-  visible = false;
-}
-</script>
+);
 
-<style scoped>
-.active-route {
-  color: #4d66fd;
-}
+function toMenu(menu: MenuItem) {
+  console.log(menu, curMenu.value, route.name);
 
-.qm-logo-img {
-  content: url(@/assets/qm-logo.png);
-  height: 80px;
-}
+  curMenu.value = menu.url;
 
-.basic-icon {
-  background-image: url(./images/基础管理.png);
-  margin-right: -2px;
-  width: 16px;
-  height: 16px;
-}
+  if (route.name === menu.url) return;
 
-.project-icon {
-  background-image: url(./images/项目管理.png);
-  margin-right: -2px;
-  width: 16px;
-  height: 16px;
+  router.push({
+    name: menu.url,
+  });
 }
 
-.current-location-icon {
-  background-image: url(./images/当前位置.png);
-  margin-left: 20px;
-  display: inline-block;
-  width: 10px;
-  height: 10px;
+const modifyPwdRef = ref();
+function toModifyPwd() {
+  modifyPwdRef.value?.open();
 }
 
-.logout-icon {
-  background-image: url(./images/退出.png);
-  width: 32px;
-  height: 32px;
+async function doLogout() {
+  await logout().then(() =>
+    routeLogout({ cause: "主动退出", redirectTo: "/" })
+  );
 }
-</style>
+</script>

+ 36 - 0
src/components/SvgIcon.vue

@@ -0,0 +1,36 @@
+<template>
+  <svg aria-hidden="true" class="svg-icon">
+    <use :xlink:href="symbolId" :fill="color" />
+  </svg>
+</template>
+
+<script setup lang="ts">
+import { computed } from "vue";
+
+const props = defineProps({
+  prefix: {
+    type: String,
+    default: "icon",
+  },
+  name: {
+    type: String,
+    required: true,
+  },
+  color: {
+    type: String,
+    default: "#262626",
+    // active:#165DFF
+  },
+});
+
+const symbolId = computed(() => `#${props.prefix}-${props.name}`);
+</script>
+
+<style lang="less">
+.svg-icon {
+  display: inline-block;
+  vertical-align: middle;
+  width: 16px;
+  height: 16px;
+}
+</style>

+ 54 - 0
src/constants/menu.ts

@@ -0,0 +1,54 @@
+export interface MenuItem {
+  name: string;
+  icon: string;
+  url: string;
+}
+type OriginMenuItem = MenuItem & { privileges: string[] };
+
+export const menuList = [
+  {
+    name: "学校管理",
+    icon: "school-manage",
+    url: "RoutOrg",
+    privileges: ["SUPER_ADMIN"],
+  },
+  {
+    name: "授权管理",
+    icon: "auth-manage",
+    url: "AuthManagement",
+    privileges: ["SUPER_ADMIN"],
+  },
+  {
+    name: "用户管理",
+    icon: "user-manage",
+    url: "UserManagement",
+    privileges: ["SUPER_ADMIN", "ORG_ADMIN", "ROOT_ORG_ADMIN"],
+  },
+  {
+    name: "科目管理",
+    icon: "course-manage",
+    url: "CourseManagement",
+    privileges: ["ORG_ADMIN", "ROOT_ORG_ADMIN", "COURSE_ADMIN"],
+  },
+  {
+    name: "项目列表",
+    icon: "project-manage",
+    url: "ProjectManagement",
+    privileges: ["ORG_ADMIN", "ROOT_ORG_ADMIN", "COURSE_ADMIN"],
+  },
+  {
+    name: "关联分析",
+    icon: "project-compare-manage",
+    url: "ProjectCompareManagement",
+    privileges: ["ORG_ADMIN", "ROOT_ORG_ADMIN", "COURSE_ADMIN"],
+  },
+];
+
+export const menuRelative = {
+  RoutOrg: ["RoutOrg"],
+  AuthManagement: ["AuthManagement"],
+  UserManagement: ["UserManagement"],
+  CourseManagement: ["CourseManagement"],
+  ProjectManagement: ["ProjectManagement"],
+  ProjectCompareManagement: ["ProjectCompareManagement"],
+};

BIN
src/features/login/left.png


+ 81 - 0
src/features/userManagement/ModifyPwd.vue

@@ -0,0 +1,81 @@
+<template>
+  <a-modal v-model:visible="visible" title="修改密码" :width="500">
+    <a-form
+      ref="formRef"
+      :labelCol="{ style: { width: '90px' } }"
+      :model="formData"
+      :rules="rules"
+    >
+      <a-form-item label="新密码" name="newPassword">
+        <a-input-password
+          v-model:value="formData.newPassword"
+        ></a-input-password>
+      </a-form-item>
+      <a-form-item label="确认秘密" name="newPasswordCopy">
+        <a-input-password
+          v-model:value="formData.newPasswordCopy"
+        ></a-input-password>
+      </a-form-item>
+    </a-form>
+
+    <template #footer>
+      <a-button type="primary" :disabled="loading" @click="confirm"
+        >确认</a-button
+      >
+      <a-button @click="close">取消</a-button>
+    </template>
+  </a-modal>
+</template>
+
+<script setup lang="ts">
+import useModal from "@/hooks/modal";
+import { reactive, ref } from "vue";
+import type { Rule, FormInstance } from "ant-design-vue/es/form";
+import { password } from "@/utils/formRules";
+import useLoading from "@/hooks/loading";
+import { changePassword } from "@/api/userManagementPage";
+import { message } from "ant-design-vue";
+
+/* modal */
+const { visible, open, close } = useModal();
+defineExpose({ open, close });
+
+const defaultFormData = {
+  newPassword: "",
+  newPasswordCopy: "",
+};
+type FormDataType = typeof defaultFormData;
+
+const formRef = ref<FormInstance>();
+const formData = reactive<FormDataType>({ ...defaultFormData });
+const rules: Record<keyof FormDataType, Rule[]> = {
+  newPassword: password,
+  newPasswordCopy: [
+    ...password,
+    {
+      validator: (_rule: Rule, value: string) => {
+        if (value !== formData.newPassword) {
+          return Promise.reject("两次输入的密码不一致");
+        } else {
+          return Promise.resolve();
+        }
+      },
+    },
+  ],
+};
+
+/* confirm */
+const { loading, setLoading } = useLoading();
+async function confirm() {
+  const err = await formRef.value?.validate();
+  if (err) return;
+
+  setLoading(true);
+
+  const res = await changePassword(formData.newPassword).catch(() => false);
+  setLoading(false);
+  if (!res) return;
+  void message.success("修改成功!");
+  close();
+}
+</script>

+ 24 - 0
src/hooks/auth.ts

@@ -0,0 +1,24 @@
+import { ref } from "vue";
+import { useMainStore } from "@/store";
+import { omit } from "lodash-es";
+import { menuList } from "@/constants/menu";
+import type { MenuItem } from "@/constants/menu";
+
+export default function useAuth(role) {
+  const store = useMainStore();
+  const menus = ref<MenuItem[]>([]);
+
+  console.log(role);
+
+  const updateMenus = () => {
+    menus.value = menuList
+      .filter((item) => item.privileges.includes(role))
+      .map((item) => omit(item, ["privileges"]));
+  };
+  updateMenus();
+
+  return {
+    menus,
+    updateMenus,
+  };
+}

+ 22 - 0
src/hooks/modal.ts

@@ -0,0 +1,22 @@
+import { ref } from 'vue';
+
+export default function useModal() {
+  const visible = ref(false);
+
+  function open() {
+    visible.value = true;
+  }
+  function close() {
+    visible.value = false;
+  }
+  function toggle() {
+    visible.value = !visible.value;
+  }
+
+  return {
+    visible,
+    open,
+    close,
+    toggle,
+  };
+}

+ 8 - 34
src/main.ts

@@ -8,6 +8,7 @@ if (!validUA) {
 
 import "./styles/index.less";
 import "./features/report/assets/report.css";
+import "virtual:svg-icons-register";
 
 import { createApp } from "vue";
 import App from "./App.vue";
@@ -17,44 +18,17 @@ import filters from "@/filters";
 import roundNumber from "@/directives/roundNumber";
 import numberToPercent from "@/directives/numberToPercent";
 
-import ECharts from "vue-echarts";
-import { use } from "echarts/core";
-
-import { CanvasRenderer, SVGRenderer } from "echarts/renderers";
-import { LineChart, BarChart } from "echarts/charts";
-import {
-  GridComponent,
-  TitleComponent,
-  TooltipComponent,
-  LegendComponent,
-  MarkLineComponent,
-} from "echarts/components";
-// import { Table } from "ant-design-vue";
-// [Table].forEach((element) => {
-//   element.props.size.default = "small";
-// });
-
-use([
-  SVGRenderer,
-  CanvasRenderer,
-  LineChart,
-  BarChart,
-  GridComponent,
-  TitleComponent,
-  TooltipComponent,
-  LegendComponent,
-  MarkLineComponent,
-]);
-
-import { createPinia } from "pinia";
-
-// if(process.env.NODE_ENV)
-// console.log(import.meta.env.DEV);
+import ECharts from "./plugins/echarts";
+import SvgIcon from "./components/SvgIcon.vue";
+
+import store from "./store";
+
 const app = createApp(App);
 app.use(router);
-app.use(createPinia());
+app.use(store);
 app.config.globalProperties.$filters = filters;
 
+app.component("SvgIcon", SvgIcon);
 app.component("VChart", ECharts);
 app.directive("round-number", roundNumber);
 app.directive("number-to-percent", numberToPercent);

+ 7 - 1
src/plugins/axiosApp.ts

@@ -8,6 +8,7 @@ import { PLATFORM, DEVICE_ID } from "@/constants/constants";
 import { message } from "ant-design-vue";
 import CryptoJS from "crypto-js";
 import { useMainStore } from "@/store";
+import { initSyncTime, fetchTime } from "./syncServerTime";
 
 let store = null as unknown as ReturnType<typeof useMainStore>;
 
@@ -43,7 +44,7 @@ _axiosApp.interceptors.request.use(
       store.globalMask = true;
     }
     const wk_token = getToken();
-    const time = Date.now();
+    const time = fetchTime();
     if (wk_token) {
       const completeURL = new URL("http://nasty.com" + config.url);
       const path = completeURL.pathname;
@@ -76,6 +77,8 @@ _axiosApp.interceptors.request.use(
 // Add a response interceptor
 _axiosApp.interceptors.response.use(
   (response) => {
+    initSyncTime(new Date(response.headers.date).getTime());
+
     if (response.config.setGlobalMask) {
       store.globalMask = false;
     }
@@ -96,6 +99,9 @@ _axiosApp.interceptors.response.use(
       }
       return Promise.reject(error);
     }
+
+    initSyncTime(new Date(error.response.headers.date).getTime());
+
     // 这里是返回状态码不为200时候的错误处理
     const status = error.response.status;
 

+ 26 - 0
src/plugins/echarts.ts

@@ -0,0 +1,26 @@
+import ECharts from "vue-echarts";
+import { use } from "echarts/core";
+
+import { CanvasRenderer, SVGRenderer } from "echarts/renderers";
+import { LineChart, BarChart } from "echarts/charts";
+import {
+  GridComponent,
+  TitleComponent,
+  TooltipComponent,
+  LegendComponent,
+  MarkLineComponent,
+} from "echarts/components";
+
+use([
+  SVGRenderer,
+  CanvasRenderer,
+  LineChart,
+  BarChart,
+  GridComponent,
+  TitleComponent,
+  TooltipComponent,
+  LegendComponent,
+  MarkLineComponent,
+]);
+
+export default ECharts;

+ 28 - 0
src/plugins/syncServerTime.ts

@@ -0,0 +1,28 @@
+let initLocalTime = 0;
+let initServerTime = 0;
+
+function getStorgeTime(): number[] {
+  const st = localStorage.getItem('st') || '';
+  const unvalidVals = ['Infinity', 'NaN', 'null', 'undefined'];
+  if (unvalidVals.includes(`${st}`)) {
+    return [Date.now(), Date.now()];
+  }
+
+  const [s, t] = st.split('_');
+  return [Number(s), Number(t)];
+}
+
+function initSyncTime(serverTime: number, localTime = Date.now()): void {
+  initLocalTime = localTime;
+  initServerTime = serverTime;
+  localStorage.setItem('st', `${initServerTime}_${initLocalTime}`);
+}
+
+function fetchTime(): number {
+  return Date.now() + initServerTime - initLocalTime;
+}
+
+const [serverTime, localTime] = getStorgeTime();
+initSyncTime(serverTime, localTime);
+
+export { initSyncTime, fetchTime };

+ 19 - 2
src/router/index.ts

@@ -33,83 +33,100 @@ const routes = [
   { path: "/login", component: Login, name: "Login" },
   {
     path: "/basic",
+    component: Layout,
     children: [
       {
         path: "rootOrg",
         component: RoutOrg,
+        name: "RoutOrg",
       },
       {
         path: "auth",
         component: AuthManagement,
+        name: "AuthManagement",
       },
       {
         path: "rootOrg/edit/:orgId",
         component: RootOrgEdit,
+        name: "RootOrgEdit",
       },
       {
         path: "subOrg",
         component: SubOrg,
+        name: "SubOrg",
       },
       {
         path: "role",
         component: RoleManagement,
+        name: "RoleManagement",
       },
       {
         path: "user",
         component: UserManagement,
+        name: "UserManagement",
       },
       {
         path: "user/privilege/:userId",
         component: UserPrivilege,
+        name: "UserPrivilege",
       },
       {
         path: "course",
         component: CourseManagement,
+        name: "CourseManagement",
       },
     ],
-    component: Layout,
   },
   {
     path: "/project",
+    component: Layout,
     children: [
       {
         path: "projectManagement",
         component: ProjectManagement,
+        name: "ProjectManagement",
       },
       {
         path: "datasource/:projectId",
         component: ProjectDataManagement,
+        name: "ProjectDataManagement",
       },
       {
         path: "params/:projectId",
         component: ProjectParamsManagement,
+        name: "ProjectParamsManagement",
       },
       {
         path: "papers/:projectId",
         component: ProjectPapersManagement,
+        name: "ProjectPapersManagement",
       },
       {
         path: ":projectId/paperAnalysis/:paperId",
         component: PaperAnalysis,
+        name: "PaperAnalysis",
       },
       {
         path: "allAnalysis/:projectId",
         component: AllAnalysis2,
+        name: "AllAnalysis2",
       },
       {
         path: "projectCompareManagement",
         component: ProjectCompareManagement,
+        name: "ProjectCompareManagement",
       },
       {
         path: "projectCompareDetail",
         component: ProjectCompareDetail2,
+        name: "ProjectCompareDetail2",
       },
       {
         path: "taskManagement",
         component: TaskManagement,
+        name: "TaskManagement",
       },
     ],
-    component: Layout,
   },
   {
     path: "/report/:viewType/:projectId/:paperId/:compareProjectId?",

+ 8 - 62
src/store/index.ts

@@ -1,64 +1,10 @@
-import { setSessionId, setToken } from "@/auth/auth";
-import { RoleCode } from "@/types";
-import { defineStore } from "pinia";
+import { createPinia } from "pinia";
+import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
 
-// main is the name of the store. It is unique across your application
-// and will appear in devtools
-export const useMainStore = defineStore("main", {
-  // a function that returns a fresh state
-  state: () => {
-    return {
-      globalMask: false,
-      userInfo: {
-        userId: -1,
-        identity: "",
-        displayName: "",
-        rootOrgId: -1,
-        rootOrgName: "",
-        token: "",
-        role: 'COURSE_ADMIN' as RoleCode,
-      },
-      currentLocation: "",
-    };
-  },
-  getters: {
-    isSuperAdmin(): boolean {
-      return this.userInfo.role === "SUPER_ADMIN";
-    },
-    isRootOrgAdmin(): boolean {
-      return this.userInfo.role === "ROOT_ORG_ADMIN";
-    },
-    isOrgAdmin(): boolean {
-      return this.userInfo.role === "ORG_ADMIN";
-    },
-    isGreaterThanEqualRootOrgAdmin(): boolean {
-      return this.isSuperAdmin || this.isRootOrgAdmin;
-    },
-    isGreaterThanEqualOrgAdmin(): boolean {
-      return this.isSuperAdmin || this.isRootOrgAdmin || this.isOrgAdmin;
-    },
-    isCourseAdmin():boolean {
-      return this.userInfo.role === "COURSE_ADMIN";
-    }
-  },
-  actions: {
-    setUserInfo(res: any) {
-      this.userInfo = res;
-      setToken(this.userInfo.token);
-      setSessionId(this.userInfo.identity);
-    },
-  },
-});
+import useMainStore from "./main";
 
-setTimeout(() => {
-  const store = useMainStore();
-  store.$subscribe((_, state) =>
-    sessionStorage.setItem("appState", JSON.stringify(state))
-  );
-  const state = JSON.parse(
-    sessionStorage.getItem("appState") || "{}"
-  ) as ReturnType<typeof useMainStore>;
-  if (state.userInfo?.token) {
-    Object.assign(store, state);
-  }
-}, 0);
+const pinia = createPinia();
+pinia.use(piniaPluginPersistedstate);
+
+export { useMainStore };
+export default pinia;

+ 58 - 0
src/store/main.ts

@@ -0,0 +1,58 @@
+import { setSessionId, setToken } from "@/auth/auth";
+import { RoleCode } from "@/types";
+import { defineStore } from "pinia";
+
+// main is the name of the store. It is unique across your application
+// and will appear in devtools
+const useMainStore = defineStore("main", {
+  // a function that returns a fresh state
+  state: () => {
+    return {
+      globalMask: false,
+      userInfo: {
+        userId: -1,
+        identity: "",
+        displayName: "",
+        rootOrgId: -1,
+        rootOrgName: "",
+        token: "",
+        role: "" as RoleCode,
+      },
+      currentLocation: "",
+    };
+  },
+  getters: {
+    isSuperAdmin(): boolean {
+      return this.userInfo.role === "SUPER_ADMIN";
+    },
+    isRootOrgAdmin(): boolean {
+      return this.userInfo.role === "ROOT_ORG_ADMIN";
+    },
+    isOrgAdmin(): boolean {
+      return this.userInfo.role === "ORG_ADMIN";
+    },
+    isGreaterThanEqualRootOrgAdmin(): boolean {
+      return this.isSuperAdmin || this.isRootOrgAdmin;
+    },
+    isGreaterThanEqualOrgAdmin(): boolean {
+      return this.isSuperAdmin || this.isRootOrgAdmin || this.isOrgAdmin;
+    },
+    isCourseAdmin(): boolean {
+      return this.userInfo.role === "COURSE_ADMIN";
+    },
+  },
+  actions: {
+    setUserInfo(res: any) {
+      this.$patch({
+        userInfo: res,
+      });
+      setToken(res.token);
+      setSessionId(res.identity);
+    },
+  },
+  persist: {
+    storage: sessionStorage,
+  },
+});
+
+export default useMainStore;

+ 33 - 0
src/styles/ant-custom.less

@@ -19,4 +19,37 @@
     background-color: var(--color-success-light);
     color: var(--color-success);
   }
+  .ant-message-warning {
+    border-color: var(--color-warning);
+    background-color: var(--color-warning-light);
+    color: var(--color-warning);
+  }
+}
+
+// ant-btn
+.ant-btn + .ant-btn {
+  margin-left: 8px;
+}
+
+// ant-modal
+.ant-modal {
+  .ant-modal-content {
+    padding: 0;
+  }
+  .ant-modal-header {
+    padding: 20px;
+    border-bottom: 1px solid var(--color-border);
+    margin: 0;
+  }
+  .ant-modal-close {
+    width: 24px;
+    height: 24px;
+  }
+  .ant-modal-body {
+    padding: 20px;
+  }
+  .ant-modal-footer {
+    padding: 0 20px 20px;
+    margin: 0;
+  }
 }

+ 0 - 32
src/styles/global.less

@@ -1,15 +1,3 @@
-@import "./var.less";
-@import "./reset.less";
-
-body {
-  margin: 0;
-  font-size: var(--app-main-font-size);
-  /* min-width: var(--app-min-width); */
-  background-color: var(--app-main-bg-color);
-  min-height: 600px;
-  user-select: none;
-}
-
 .main-text-color {
   color: var(--app-main-text-color);
 }
@@ -18,23 +6,3 @@ body {
   color: var(--app-small-header-text-color);
   font-size: var(--app-secondary-font-size);
 }
-
-/* modal 不要“取消按钮” */
-.ant-modal-content .ant-modal-footer button:first-child {
-  display: none;
-}
-
-.ant-popover-arrow {
-  border-top-color: #4c4e5c !important;
-  border-left-color: #4c4e5c !important;
-}
-.ant-popover-inner-content {
-  background: linear-gradient(180deg, #4c4e5c 0%, #3d404e 100%);
-  box-shadow: 0px 4px 8px 0px rgba(0, 0, 0, 0.3);
-  opacity: 0.98;
-  border-radius: 10px;
-  /* color: rgba(0, 0, 0, 0.3); */
-}
-.ant-select-clear {
-  margin-top: -8px !important;
-}

+ 99 - 0
src/styles/home.less

@@ -0,0 +1,99 @@
+.home {
+  &-header {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 56px;
+    padding: 12px 16px;
+    background-color: #fff;
+
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+
+  &-menu {
+    font-size: 0;
+
+    .home-title {
+      display: inline-block;
+      vertical-align: middle;
+      margin-right: 16px;
+      font-size: var(--app-title-font-size);
+      height: 24px;
+      font-weight: bold;
+      line-height: 24px;
+
+      padding-left: 32px;
+      background-image: url(../assets/bg-login-title.jpg);
+      background-size: auto 100%;
+      background-repeat: no-repeat;
+    }
+
+    &-item {
+      font-size: var(--app-main-font-size);
+      display: inline-block;
+      vertical-align: middle;
+      margin-right: 8px;
+      padding: 5px 16px;
+      border-radius: 6px;
+      line-height: 22px;
+      cursor: pointer;
+
+      .svg-icon {
+        margin-right: 6px;
+      }
+      > span {
+        display: inline-block;
+        vertical-align: middle;
+      }
+
+      &.is-active,
+      &:hover {
+        color: var(--color-primary);
+        background-color: var(--color-primary-light);
+
+        .svg-icon use {
+          fill: var(--color-primary);
+        }
+      }
+    }
+  }
+
+  &-action {
+    &-item {
+      display: inline-block;
+      vertical-align: middle;
+      margin-right: 8px;
+      padding: 5px;
+      line-height: 22px;
+
+      .svg-icon {
+        margin-right: 6px;
+      }
+      > span {
+        display: inline-block;
+        vertical-align: middle;
+      }
+    }
+  }
+
+  &-body {
+    position: absolute;
+    top: 56px;
+    bottom: 0;
+    width: 100%;
+    background-color: var(--color-background);
+    overflow: auto;
+    padding: 16px;
+  }
+
+  &-breadcrumb {
+    height: 22px;
+    font-weight: 400;
+    color: var(--app-secondary-text-color);
+    line-height: 22px;
+    margin-bottom: 16px;
+  }
+}

+ 3 - 1
src/styles/index.less

@@ -1,4 +1,6 @@
-@import "./global.less";
+@import "./var.less";
+@import "./reset.less";
+
 @import "./nprogress.less";
 @import "./ant-custom.less";
 

+ 0 - 7
src/styles/login.less

@@ -100,13 +100,6 @@
   }
 }
 .login-form {
-  .login-submit-btn {
-    width: 100%;
-    height: 48px;
-    border-radius: 24px;
-    font-size: 18px;
-  }
-
   .ant-form-item {
     margin-bottom: 22px !important;
   }

+ 13 - 4
src/styles/reset.less

@@ -92,8 +92,8 @@ textarea:focus {
 
 /* browse style */
 ::-webkit-scrollbar {
-  width: 8px;
-  height: 8px;
+  width: 6px;
+  height: 6px;
   background: transparent;
 }
 
@@ -106,8 +106,8 @@ textarea:focus {
 }
 
 ::-webkit-scrollbar-thumb {
-  background: #666;
-  border-radius: 8px;
+  background: #7584ac;
+  border-radius: 6px;
 }
 
 ::-webkit-scrollbar-corner {
@@ -117,3 +117,12 @@ textarea:focus {
 ::-webkit-scrollbar-resizer {
   background: transparent;
 }
+
+body {
+  margin: 0;
+  font-size: var(--app-main-font-size);
+  min-width: var(--app-min-width);
+  background-color: var(--app-main-bg-color);
+  min-height: 600px;
+  user-select: none;
+}

+ 1 - 0
src/styles/var.less

@@ -26,6 +26,7 @@
   --color-success: #00b42a;
   --color-success-light: #e8ffea;
   --color-warning: #ff9427;
+  --color-warning-light: #fff7e8;
   --color-danger: #f53f3f;
   --color-danger-light: #ffece8;
   --color-cyan: #2abcff;

+ 78 - 0
src/utils/formRules.ts

@@ -0,0 +1,78 @@
+import type { Rule } from "ant-design-vue/es/form";
+
+export const username: Rule[] = [
+  {
+    required: true,
+    match: /^[a-zA-Z0-9][a-zA-Z0-9_]{2,19}$/,
+    message: "用户名必须以字母或数字开头,长度为3-20位,允许字母数字下划线",
+  },
+];
+
+export const password: Rule[] = [
+  {
+    required: true,
+    match: /^[a-zA-Z0-9_]{6,20}$/,
+    message: "密码只能由数字、字母和下划线组成,长度6-20个字符",
+  },
+];
+
+export const phone: Rule[] = [
+  {
+    required: true,
+    match: /^1\d{10}$/,
+    message: "请输入合适的手机号码",
+  },
+];
+
+export const smscode: Rule[] = [
+  {
+    required: true,
+    match: /^[a-zA-Z0-9]{4}$/,
+    message: "请输入4位短信验证码",
+  },
+];
+
+export const strictPassword: Rule[] = [
+  {
+    required: true,
+    validator: (value, callback) => {
+      if (!value) {
+        return callback(`请输入密码`);
+      }
+      // // 禁止使用相同的数字或字符作为密码
+      // const reg2 = /^[a-zA-Z0-9].+$/;
+      // const vals = new Set(value.split(""));
+      // if (reg2.test(value) && vals.size === 1) {
+      //   return callback(`禁止使用相同的数字或字符作为密码`);
+      // }
+      // // 禁止使用连续升序或降序的数字或字母作为密码
+      // const valCharCodes = value.split("").map((item) => item.charCodeAt());
+      // let intervals = [];
+      // for (let i = 0; i < valCharCodes.length; i++) {
+      //   const element = valCharCodes[i];
+      //   if (i === 0) continue;
+      //   intervals.push(element - valCharCodes[i - 1]);
+      // }
+      // const interVals = Array.from(new Set(intervals));
+      // if (
+      //   reg2.test(value) &&
+      //   interVals.length === 1 &&
+      //   Math.abs(interVals[0]) === 1
+      // ) {
+      //   return callback(
+      //     `禁止使用连续升序或降序的数字或字母作为密码`
+      //   );
+      // }
+      // 密码应至少包含数字、大小写字母及特殊宇符中的两种;
+      const reg1 =
+        /^(?![\d]+$)(?![a-z]+$)(?![A-Z]+$)(?![!@#$%^&*]+$)[\da-zA-Z!@#$%^&*]{8,20}$/g;
+      if (!reg1.test(value)) {
+        return callback(
+          `密码应至少包含数字、大小写字母及特殊宇符(!@#$%^&*)中的两种,长度8-20位`
+        );
+      }
+
+      return callback();
+    },
+  },
+];

+ 23 - 2
vite.config.ts

@@ -2,12 +2,17 @@ import { defineConfig } from "vite";
 import vue from "@vitejs/plugin-vue";
 import Components from "unplugin-vue-components/vite";
 import { AntDesignVueResolver } from "unplugin-vue-components/resolvers";
+import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
+import { resolve } from "path";
+
+const resolvePath = (...args) => {
+  return resolve(__dirname, ...args);
+};
 
 // const SERVER_URL = "http://192.168.10.108:7180";
 // const SERVER_URL = "http://192.168.10.138:13800";
 // const SERVER_URL = "http://192.168.10.39:7180";
 const SERVER_URL = "http://192.168.10.106:7100";
-const path = require("path");
 
 // https://vitejs.dev/config/
 export default defineConfig({
@@ -22,6 +27,22 @@ export default defineConfig({
         }),
       ],
     }),
+    createSvgIconsPlugin({
+      iconDirs: [resolvePath("src/assets/svgs")],
+      symbolId: "icon-[name]",
+      customDomId: "__svg__icons__dom__",
+      svgoOptions: {
+        full: true,
+        plugins: [
+          {
+            name: "removeAttrs",
+            params: {
+              attrs: ["class", "data-name", "fill", "stroke"],
+            },
+          },
+        ],
+      },
+    }),
   ],
   server: {
     port: 9000,
@@ -38,7 +59,7 @@ export default defineConfig({
     },
   },
   resolve: {
-    alias: [{ find: "@", replacement: path.resolve(__dirname, "./src") }],
+    alias: [{ find: "@", replacement: resolve(__dirname, "./src") }],
     extensions: [".js", ".mjs", ".ts", ".vue", ".json", ".scss", ".css"],
   },
   build: {

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 629 - 5
yarn.lock


Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно