Browse Source

feat: 登录设置

zhangjie 1 year ago
parent
commit
6689041ffb

+ 2 - 1
electron/db/createdb.ts

@@ -1,9 +1,10 @@
 import sequelize from './sequelizeInstance';
+import './models/dict';
 import './models/trackTask';
 import './models/trackTaskDetail';
 
 async function createDb() {
-  await sequelize.sync({ force: true });
+  await sequelize.sync({ force: false });
   console.log('All models were synchronized successfully.');
 }
 

+ 25 - 0
electron/db/modelApi/dict.ts

@@ -0,0 +1,25 @@
+import Dict, { DictCreationAttributes } from '../models/dict';
+
+export async function getDict(key: string) {
+  const res = await Dict.findOne({
+    where: { key },
+  }).catch((err) => {
+    console.dir(err);
+  });
+  return res ? res.dataValues.val : '';
+}
+
+export async function updateDict(data: DictCreationAttributes) {
+  const dict = await Dict.findOne({
+    where: { key: data.key },
+  });
+
+  if (dict) {
+    dict.val = data.val;
+    await dict.save();
+    return dict;
+  }
+
+  const res = await Dict.create(data);
+  return res.dataValues;
+}

+ 58 - 0
electron/db/models/dict.ts

@@ -0,0 +1,58 @@
+import {
+  Model,
+  DataTypes,
+  CreationOptional,
+  InferAttributes,
+  InferCreationAttributes,
+} from 'sequelize';
+import sequelize from '../sequelizeInstance';
+
+class Dict extends Model<
+  // eslint-disable-next-line no-use-before-define
+  InferAttributes<Dict>,
+  // eslint-disable-next-line no-use-before-define
+  InferCreationAttributes<Dict>
+> {
+  declare id: CreationOptional<number>;
+
+  declare key: string;
+
+  declare val: string;
+
+  declare createdAt: CreationOptional<Date>;
+
+  declare updatedAt: CreationOptional<Date>;
+}
+
+Dict.init(
+  {
+    id: {
+      type: DataTypes.INTEGER.UNSIGNED,
+      autoIncrement: true,
+      primaryKey: true,
+    },
+    key: {
+      type: DataTypes.STRING,
+      allowNull: false,
+    },
+    val: {
+      type: DataTypes.STRING,
+      allowNull: false,
+    },
+    createdAt: DataTypes.DATE,
+    updatedAt: DataTypes.DATE,
+  },
+  {
+    sequelize,
+    modelName: 'Dict',
+    underscored: true,
+    tableName: 'dict',
+  }
+);
+
+export type DictCreationAttributes = InferCreationAttributes<
+  Dict,
+  { omit: 'id' | 'createdAt' | 'updatedAt' }
+>;
+
+export default Dict;

+ 10 - 1
electron/db/models/trackTask.ts

@@ -24,6 +24,10 @@ class TrackTask extends Model<
   declare pathRule: string;
 
   declare status: number;
+
+  declare createdAt: CreationOptional<Date>;
+
+  declare updatedAt: CreationOptional<Date>;
 }
 
 TrackTask.init(
@@ -56,6 +60,8 @@ TrackTask.init(
       defaultValue: 0,
       // 任务状态:0:未开始,1:运行中,2:已完成
     },
+    createdAt: DataTypes.DATE,
+    updatedAt: DataTypes.DATE,
   },
   {
     sequelize,
@@ -65,6 +71,9 @@ TrackTask.init(
   }
 );
 
-export type TrackTaskCreationAttributes = InferCreationAttributes<TrackTask>;
+export type TrackTaskCreationAttributes = InferCreationAttributes<
+  TrackTask,
+  { omit: 'id' | 'createdAt' | 'updatedAt' }
+>;
 
 export default TrackTask;

+ 10 - 2
electron/db/models/trackTaskDetail.ts

@@ -29,6 +29,10 @@ class TrackTaskDetail extends Model<
   declare status: number;
 
   declare error: string | null;
+
+  declare createdAt: CreationOptional<Date>;
+
+  declare updatedAt: CreationOptional<Date>;
 }
 
 TrackTaskDetail.init(
@@ -69,6 +73,8 @@ TrackTaskDetail.init(
       allowNull: true,
       comment: '错误信息',
     },
+    createdAt: DataTypes.DATE,
+    updatedAt: DataTypes.DATE,
   },
   {
     sequelize,
@@ -81,7 +87,9 @@ TrackTaskDetail.init(
 TrackTask.hasMany(TrackTaskDetail);
 TrackTaskDetail.belongsTo(TrackTask);
 
-export type TrackTaskDetailCreationAttributes =
-  InferCreationAttributes<TrackTaskDetail>;
+export type TrackTaskDetailCreationAttributes = InferCreationAttributes<
+  TrackTaskDetail,
+  { omit: 'id' | 'createdAt' | 'updatedAt' }
+>;
 
 export default TrackTaskDetail;

+ 6 - 3
electron/main/index.ts

@@ -8,14 +8,16 @@ import useElectron from './useElectron';
 function createWindow(): void {
   // Create the browser window.
   const mainWindow = new BrowserWindow({
-    width: 900,
-    height: 670,
+    width: is.dev ? 1428 : 1024,
+    height: 700,
+    minWidth: 1024,
+    minHeight: 600,
     show: false,
-    autoHideMenuBar: true,
     ...(process.platform === 'linux' ? { icon } : {}),
     webPreferences: {
       preload: join(__dirname, '../preload/index.js'),
       sandbox: false,
+      webSecurity: false,
     },
   });
 
@@ -32,6 +34,7 @@ function createWindow(): void {
   // Load the remote URL for development or the local html file for production.
   if (is.dev && process.env.ELECTRON_RENDERER_URL) {
     mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL);
+    mainWindow.webContents.openDevTools();
   } else {
     mainWindow.loadFile(join(__dirname, '../renderer/index.html'));
   }

+ 3 - 0
electron/preload/apiDb.ts

@@ -2,6 +2,7 @@ import {
   getUnfinishTrackTask,
   createTrackTask,
 } from '../db/modelApi/trackTask';
+import { getDict, updateDict } from '../db/modelApi/dict';
 import createDb from '../db/createdb';
 
 createDb();
@@ -9,6 +10,8 @@ createDb();
 const dbApi = {
   getUnfinishTrackTask,
   createTrackTask,
+  getDict,
+  updateDict,
 };
 
 export type DbApi = typeof dbApi;

+ 3 - 0
src/App.vue

@@ -1,5 +1,8 @@
 <template>
   <a-config-provider>
+    <template #empty>
+      <div class="empty-none"> </div>
+    </template>
     <router-view />
   </a-config-provider>
 </template>

+ 4 - 2
src/api/interceptor.ts

@@ -14,7 +14,7 @@ import { initSyncTime, fetchTime } from '../utils/syncServerTime';
 import { getAuthorization } from '../utils/crypto';
 import { DEVICE_ID, PLATFORM } from '../constants/app';
 import { objTypeOf, pickByNotNull } from '../utils/utils';
-import { useUserStore } from '../store';
+import { useUserStore, useAppStore } from '../store';
 
 axios.defaults.timeout = 10 * 1000;
 
@@ -26,6 +26,7 @@ let unauthMsgBoxIsShow = false;
 
 const modifyConfig = (config: AxiosRequestConfig): void => {
   const userStore = useUserStore();
+  const appStore = useAppStore();
 
   const headers = {} as AxiosRequestHeaders;
   if (userStore.accessToken) {
@@ -56,8 +57,9 @@ const modifyConfig = (config: AxiosRequestConfig): void => {
   }
   headers.deviceId = DEVICE_ID;
   headers.platform = PLATFORM;
-  headers.domain = window.location.origin;
+  headers.domain = appStore.domain;
 
+  config.url = `${appStore.domain}${config.url}`;
   config.headers = { ...(config.headers || {}), ...headers };
 };
 

+ 4 - 54
src/api/types/user.ts

@@ -1,63 +1,13 @@
 export interface LoginData {
-  loginName?: string;
-  password?: string;
-  code?: string;
-  mobileNumber?: string;
-  schoolCode: string;
-  type: 'ACCOUNT' | 'PHONE';
-}
-export interface SchoolInfoRes {
-  phoneLogin: boolean;
-  accountSmsVerify: boolean;
-  name: string;
-  logo: string;
-  version: string;
-  schoolCode: string;
-}
-export interface SmsCodeData {
-  schoolCode: string;
-  mobileNumber: string;
-}
-export interface AccountSmsCodeData {
-  schoolCode: string;
   loginName: string;
   password: string;
-}
-export interface SmsCodeForBindData {
   schoolCode: string;
-  loginName: string;
-  password: string;
-  mobileNumber: string;
-}
-export interface UpdatePwdData {
-  id: string;
-  oldPassword: string;
-  verifyCode?: string;
-  mobileNumber?: string;
-  password?: string;
+  type: 'ACCOUNT' | 'PHONE';
 }
 
-export type UserMenuPrivilegeEnum =
-  | 'conditions'
-  | 'buttons'
-  | 'lists'
-  | 'links';
-
-export interface UserMenuBaseItem {
+export interface SchoolItem {
   id: string;
   name: string;
-  url: string;
-  type: string;
-  parentId: string;
-  sequence: number;
-}
-export interface UserMenuItem extends UserMenuBaseItem {
-  conditions?: UserMenuBaseItem[];
-  buttons?: UserMenuBaseItem[];
-  lists?: UserMenuBaseItem[];
-  links?: UserMenuBaseItem[];
-}
-export interface UserMenuRes {
-  userId: string;
-  privileges: UserMenuItem[];
+  code: string;
+  logo: string;
 }

+ 3 - 38
src/api/user.ts

@@ -1,50 +1,15 @@
 import axios from 'axios';
 import { UserState } from '@/store/modules/user/types';
-import type {
-  LoginData,
-  SchoolInfoRes,
-  SmsCodeData,
-  AccountSmsCodeData,
-  SmsCodeForBindData,
-  UpdatePwdData,
-  UserMenuRes,
-} from './types/user';
+import type { LoginData, SchoolItem } from './types/user';
 
 export function login(data: LoginData): Promise<UserState> {
   return axios.post('/api/admin/common/login', data);
 }
 
-export function getSchoolInfo(code: string): Promise<SchoolInfoRes> {
-  return axios.post(
-    '/api/admin/common/school/query_by_school_code',
-    {},
-    { params: { code } }
-  );
-}
-
-interface SmsCodeRes {
-  mobileNumber: string;
-}
-export function getSmsCode(datas: SmsCodeData): Promise<SmsCodeRes> {
-  return axios.post('/api/admin/common/get_verify_code', datas);
-}
-export function getAccountSmsCode(datas: AccountSmsCodeData): Promise<string> {
-  return axios.post('/api/admin/common/get_verify_code_by_account', datas);
-}
-export function getSmsCodeForBind(
-  datas: SmsCodeForBindData
-): Promise<SmsCodeRes> {
-  return axios.post('/api/admin/common/get_verify_code_for_bind', datas);
-}
-
-export function updatePwd(datas: UpdatePwdData): Promise<UserState> {
-  return axios.post('/api/admin/sys/user/update_password', datas);
+export function schoolList(): Promise<SchoolItem[]> {
+  return axios.post('/api/admin/common/school/list', {});
 }
 
 export function userLogout() {
   return axios.post('/api/admin/common/logout', {});
 }
-
-export function sysMenu(): Promise<UserMenuRes> {
-  return axios.post('/api/admin/common/get_menu', {});
-}

BIN
src/assets/images/bg-login-spin.png


BIN
src/assets/images/bg-login.png


BIN
src/assets/images/bg-logo.png


BIN
src/assets/images/icon-setting-act.png


BIN
src/assets/images/icon-setting.png


+ 12 - 0
src/assets/styles/base.less

@@ -96,6 +96,18 @@
   }
 }
 
+// empty-none
+.empty-none {
+  text-align: center;
+  padding: 10px 0;
+  height: 95px;
+
+  background-image: url(../images/bg-empty.png);
+  background-size: 98px 74px;
+  background-position: center;
+  background-repeat: no-repeat;
+}
+
 /* table */
 .table {
   width: 100%;

+ 0 - 570
src/assets/styles/home copy.less

@@ -1,570 +0,0 @@
-/* home */
-.home {
-  position: absolute;
-  width: 100%;
-  height: 100%;
-  z-index: auto;
-}
-
-.home-body {
-  position: absolute;
-  left: 0;
-  top: 50px;
-  right: 0;
-  bottom: 0;
-  overflow: auto;
-  background: var(--color-background);
-  z-index: 98;
-}
-
-.home-body-content {
-  position: relative;
-  padding: 20px 20px 50px 240px;
-  min-height: 100%;
-}
-
-.home-page-main {
-  .home-navs {
-    bottom: auto;
-    height: 50px;
-    border-radius: 0;
-    background-color: transparent;
-
-    &::before {
-      display: none;
-    }
-
-    .head-logo-content {
-      display: none;
-      padding: 5px 0;
-      border: none;
-    }
-
-    .arco-menu {
-      display: none;
-    }
-  }
-
-  .home-breadcrumb {
-    display: none;
-  }
-
-  .home-body-content {
-    padding-left: 30px;
-  }
-}
-
-/* navs */
-.home-navs {
-  position: absolute;
-  width: 220px;
-  top: 56px;
-  left: 0;
-  bottom: 0;
-  z-index: 100;
-  overflow: auto;
-  font-size: 14px;
-  background: var(--color-white);
-}
-
-.arco-menu-home {
-  padding: 16px 8px;
-
-  .arco-menu-inner {
-    padding: 0;
-  }
-
-  .arco-menu-inline {
-    margin-bottom: 20px;
-  }
-
-  .arco-menu-inline-header {
-    padding: 9px 40px !important;
-    min-height: 38px;
-    line-height: 20px;
-    font-weight: 400;
-
-    .arco-menu-icon {
-      position: absolute;
-      left: 16px;
-      font-size: 16px;
-    }
-
-    &.arco-menu-selected {
-      &:hover {
-        background-color: var(--color-primary-light);
-      }
-    }
-  }
-
-  .arco-menu-item {
-    height: auto;
-    min-height: 38px;
-    line-height: 20px;
-    padding: 9px 40px !important;
-    white-space: normal;
-    .arco-menu-indent-list {
-      display: none;
-    }
-
-    &.arco-menu-selected {
-      font-weight: 500;
-      color: var(--color-primary);
-      background-color: var(--color-primary-light);
-    }
-  }
-}
-
-/* head */
-.home-header {
-  position: absolute;
-  width: 100%;
-  height: 56px;
-  top: 0;
-  left: 0;
-  z-index: 99;
-  padding: 12px 16px 11px;
-  background-color: #fff;
-
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  border-bottom: 1px solid var(--color-border);
-
-  .home-title {
-    font-size: 20px;
-    font-weight: bold;
-  }
-
-  .home-action {
-    &-item {
-      display: inline-block;
-      vertical-align: middle;
-      height: 32px;
-      line-height: 32px;
-      padding: 0 8px;
-      font-weight: 400;
-      border-radius: var(--border-radius-small);
-
-      &:not(:first-child) {
-        margin-left: 8px;
-      }
-
-      .svg-icon {
-        font-size: 18px;
-      }
-
-      .svg-icon + span {
-        margin-left: 6px;
-      }
-      > span {
-        display: inline-block;
-        vertical-align: middle;
-      }
-
-      &.cursor {
-        cursor: pointer;
-
-        &:hover {
-          background-color: var(--color-background);
-        }
-      }
-    }
-  }
-}
-
-// menu-dialog
-.menu-dialog {
-  .arco-dialog.is-fullscreen {
-    border-radius: 0;
-    box-shadow: none;
-
-    .arco-dialog__body {
-      padding: 10px;
-
-      &::after {
-        display: none;
-      }
-    }
-  }
-
-  .menu-logout {
-    padding: 10px;
-    width: 52px;
-    height: 52px;
-    margin: 0 auto;
-    border: 1px solid var(--color-text-gray-1);
-    border-radius: 50%;
-    font-size: 30px;
-    text-align: center;
-    color: var(--color-text-gray-1);
-    cursor: pointer;
-
-    &:hover {
-      border-color: var(--color-danger);
-      color: var(--color-danger);
-    }
-  }
-}
-
-// home-breadcrumb
-.home-breadcrumb {
-  margin-bottom: 16px;
-
-  .breadcrumb-tips {
-    display: inline-block;
-    vertical-align: middle;
-    color: var(--color-text-gray);
-
-    > .svg-icon {
-      margin-top: -3px;
-      margin-right: 5px;
-      font-size: 14px;
-    }
-  }
-
-  .arco-breadcrumb {
-    display: inline-block;
-    vertical-align: middle;
-
-    .arco-breadcrumb-item {
-      line-height: 22px;
-      color: var(--color-text-gray);
-    }
-
-    .arco-breadcrumb-item-separator {
-      margin: 0;
-    }
-  }
-}
-
-// home-view
-
-/* view-footer */
-.home-footer {
-  position: absolute;
-  width: 100%;
-  height: 30px;
-  bottom: 0;
-  left: 0;
-  z-index: 99;
-  padding: 5px 0;
-  line-height: 20px;
-  color: var(--color-text-gray-1);
-  text-align: center;
-  font-size: 13px;
-  background-color: var(--color-background);
-
-  a {
-    color: var(--color-text-gray-1);
-  }
-
-  a:hover {
-    color: var(--color-text-gray);
-  }
-}
-
-// home-page
-.home-page {
-  width: 1200px;
-  margin: 0 auto;
-
-  .hp-top {
-    display: flex;
-    justify-content: space-between;
-    align-items: stretch;
-
-    &-left {
-      flex-grow: 0;
-      flex-shrink: 0;
-      width: 340px;
-      margin-right: 20px;
-    }
-
-    &-right {
-      flex-grow: 2;
-    }
-  }
-
-  .tab-summary {
-    margin-bottom: 10px;
-    font-size: 0;
-
-    &-item {
-      position: relative;
-      display: inline-block;
-      width: 100%;
-      vertical-align: top;
-      margin-right: 10px;
-      border-radius: 12px;
-      padding: 16px 20px 16px;
-      color: #fff;
-      text-align: right;
-      background: #3a5ae5;
-
-      &.item-done {
-        background: #9877ff;
-      }
-
-      &.item-wait {
-        cursor: pointer;
-        &:hover {
-          opacity: 0.8;
-        }
-      }
-    }
-
-    &-rp {
-      display: block;
-      position: absolute;
-      width: 10px;
-      height: 10px;
-      background: #fe646a;
-      border: 2px solid #eff0f5;
-      top: -2px;
-      left: -2px;
-      border-radius: 50%;
-      z-index: 9;
-    }
-
-    &-icon {
-      display: block;
-      position: absolute;
-      top: 16px;
-      left: 16px;
-      z-index: auto;
-    }
-
-    &-title {
-      font-size: 12px;
-      margin-bottom: 7px;
-      opacity: 0.6;
-    }
-
-    &-cont {
-      height: 38px;
-      font-size: 32px;
-      font-weight: bold;
-      line-height: 38px;
-    }
-  }
-
-  .tab-box {
-    border-radius: 12px;
-    background-color: #fff;
-    margin-bottom: 20px;
-    min-height: 100px;
-    padding: 20px;
-
-    &-title {
-      font-weight: bold;
-      font-size: 16px;
-      line-height: 1;
-      color: #1f222f;
-      margin-bottom: 10px;
-    }
-  }
-
-  .shortcut-list {
-    font-size: 0;
-    margin-top: 20px;
-  }
-
-  .shortcut-item {
-    display: inline-block;
-    vertical-align: top;
-    padding: 0 18px;
-    width: 180px;
-    border-radius: 8px;
-    border: 1px solid #eff0f5;
-    height: 52px;
-    line-height: 50px;
-    margin: 10px;
-    text-align: left;
-    cursor: pointer;
-
-    &:hover {
-      opacity: 0.7;
-    }
-
-    > * {
-      display: inline-block;
-      vertical-align: middle;
-    }
-  }
-
-  .icon {
-    margin-right: 18px;
-  }
-
-  .shortcut-name {
-    font-size: 14px;
-    font-weight: 500;
-    color: #434656;
-  }
-
-  .tab-task {
-    min-height: 190px;
-  }
-  .tab-task-item {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    padding: 10px 0;
-
-    &:not(:last-child) {
-      border-bottom: 1px solid #eff0f5;
-    }
-
-    &-cont {
-      flex-grow: 2;
-      margin: 0 10px;
-      white-space: nowrap;
-      text-overflow: ellipsis;
-      overflow: hidden;
-      line-height: 24px;
-      cursor: pointer;
-
-      &:hover {
-        opacity: 0.7;
-      }
-    }
-
-    &-type {
-      flex-shrink: 0;
-      flex-grow: 0;
-      font-size: 12px;
-      color: rgba(58, 90, 229, 1);
-      border-radius: 6px;
-      text-align: center;
-      width: 36px;
-      height: 24px;
-      line-height: 24px;
-      background: rgba(58, 90, 229, 0.1);
-    }
-  }
-
-  .guide-map {
-    padding: 40px 0;
-    text-align: center;
-  }
-  .guide-item {
-    display: inline-block;
-    vertical-align: middle;
-    position: relative;
-    margin: 0 10px;
-
-    &:not(:last-child):after {
-      content: '';
-      display: block;
-      position: absolute;
-      width: 20px;
-      height: 10px;
-      background-image: url(../images/icon-narrow.png);
-      background-size: 100% 100%;
-      background-repeat: no-repeat;
-
-      top: 50%;
-      right: -20px;
-      margin-top: -5px;
-    }
-  }
-
-  .guide-spin {
-    width: 136px;
-    height: 136px;
-    background: #afb3c7;
-    border-radius: 68px;
-    display: table-cell;
-    vertical-align: middle;
-    text-align: center;
-    color: #1f222f;
-    font-size: 14px;
-    font-weight: bold;
-
-    &.spin-large {
-      width: 192px;
-      height: 192px;
-      border-radius: 96px;
-    }
-
-    &.spin-small {
-      width: 108px;
-      height: 108px;
-      border-radius: 54px;
-    }
-
-    &.spin-light {
-      background: #e1e3eb;
-    }
-  }
-  .island-item {
-    width: 282px;
-    background: #afb3c7;
-    border-radius: 20px;
-    padding: 30px;
-    border: 2px solid #fff;
-    position: relative;
-  }
-  .island-bottom {
-    background: #e1e3eb;
-    &::after {
-      content: '';
-      display: block;
-      position: absolute;
-      width: 40px;
-      border-width: 10px 20px;
-      border-style: solid;
-      border-color: transparent #e1e3eb transparent transparent;
-      top: -10px;
-      right: 50%;
-      z-index: 9;
-    }
-  }
-  .island-top {
-    &::after {
-      content: '';
-      display: block;
-      position: absolute;
-      width: 40px;
-      border-width: 10px 20px;
-      border-style: solid;
-      border-color: transparent transparent transparent #afb3c7;
-      bottom: -10px;
-      left: 50%;
-      z-index: 9;
-    }
-
-    .island-btn {
-      width: 100%;
-    }
-  }
-  .island-title {
-    font-size: 14px;
-    font-weight: bold;
-    color: #1f222f;
-    line-height: 21px;
-    margin-bottom: 20px;
-    text-align: left;
-  }
-  .island-cont {
-    font-size: 0;
-  }
-  .island-btn {
-    display: inline-block;
-    vertical-align: top;
-    height: 40px;
-    background: #ffffff;
-    border-radius: 6px;
-    font-weight: 500;
-    color: #434656;
-    padding: 14px 20px;
-    font-size: 12px;
-    line-height: 1;
-    text-align: center;
-
-    & + .island-btn {
-      margin-left: 10px;
-    }
-  }
-}

+ 74 - 572
src/assets/styles/home.less

@@ -9,632 +9,134 @@
 .home-body {
   position: absolute;
   left: 0;
-  top: 50px;
+  top: 0;
   right: 0;
   bottom: 0;
   overflow: auto;
-  background: var(--color-background);
   z-index: 98;
+  padding-top: 56px;
+  padding-bottom: 30px;
+  min-width: 800px;
+  min-height: 600px;
+  background: var(--color-background);
 }
 
-.home-body-content {
-  position: relative;
-  padding: 20px 20px 50px 240px;
-  min-height: 100%;
-}
-
-.home-page-main {
-  .home-navs {
-    bottom: auto;
-    height: 50px;
-    border-radius: 0;
-    background-color: transparent;
-
-    &::before {
-      display: none;
-    }
-
-    .head-logo-content {
-      display: none;
-      padding: 5px 0;
-      border: none;
-    }
-
-    .arco-menu {
-      display: none;
-    }
-  }
-
-  .home-breadcrumb {
-    display: none;
-  }
-
-  .home-body-content {
-    padding-left: 30px;
-  }
-}
-
-/* navs */
-.home-navs {
-  position: absolute;
-  width: 220px;
-  top: 56px;
-  left: 0;
-  bottom: 0;
-  z-index: 100;
-  overflow: auto;
-  font-size: 14px;
-  background: var(--color-white);
-}
-
-.arco-menu-home {
-  padding: 16px 8px;
-
-  .arco-menu-inner {
-    padding: 0;
-  }
-
-  .arco-menu-inline {
-    margin-bottom: 20px;
-  }
-
-  .arco-menu-inline-header {
-    padding: 9px 40px !important;
-    min-height: 38px;
-    line-height: 20px;
-    font-weight: 400;
-
-    .arco-menu-icon {
-      position: absolute;
-      left: 16px;
-      font-size: 16px;
-    }
-
-    &.arco-menu-selected {
-      &:hover {
-        background-color: var(--color-primary-light);
-      }
-    }
-  }
-
-  .arco-menu-item {
-    height: auto;
-    min-height: 38px;
-    line-height: 20px;
-    padding: 9px 40px !important;
-    white-space: normal;
-    .arco-menu-indent-list {
-      display: none;
-    }
-
-    &.arco-menu-selected {
-      font-weight: 500;
-      color: var(--color-primary);
-      background-color: var(--color-primary-light);
-    }
-  }
-}
-/* head */
-.home-header {
+.home-head {
   position: absolute;
   width: 100%;
   height: 56px;
   top: 0;
   left: 0;
   z-index: 99;
-  // padding: 12px 0 11px;
-  background-color: #fff;
-
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  border-bottom: 1px solid var(--color-border);
-
-  .head-logo {
-    width: 220px;
-    padding: 0 10px;
-    font-size: 20px;
-    line-height: 40px;
-    text-align: center;
-    flex-grow: 0;
-    flex-shrink: 0;
-
-    &-content {
-      height: 40px;
-      cursor: pointer;
+  background-color: #262626;
+  padding: 12px 24px;
 
-      &:hover {
-        opacity: 0.8;
-      }
-    }
-
-    img {
-      display: block;
-      max-width: 100%;
-      max-height: 100%;
-      margin: 0 auto;
+  .home-user {
+    ul {
+      font-size: 0;
     }
-  }
-
-  .home-menu {
-    font-size: 0;
-    flex-grow: 2;
-    overflow-y: hidden;
-    overflow-x: auto;
-    padding: 12px 0 11px;
-    white-space: nowrap;
-
-    &::-webkit-scrollbar {
-      width: 4px;
-      height: 4px;
-    }
-
-    &-item {
-      font-size: var(--font-size-base);
+    li {
       display: inline-block;
-      vertical-align: middle;
-      margin-right: 6px;
-      padding: 5px 10px;
-      border-radius: 6px;
-      height: 32px;
-      line-height: 22px;
-      font-weight: 400;
-      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);
-        font-weight: 500;
-
-        .svg-icon use {
-          fill: var(--color-primary);
-        }
-      }
-    }
-  }
-
-  .home-action {
-    padding: 0 16px;
-    flex-grow: 0;
-    flex-shrink: 0;
-
-    &-item {
-      display: inline-block;
-      vertical-align: middle;
+      vertical-align: top;
+      font-size: 14px;
+      border-radius: 4px;
+      border: 1px solid #686868;
       height: 32px;
-      line-height: 32px;
-      padding: 0 8px;
-      font-weight: 400;
-      border-radius: var(--border-radius-small);
-
-      &:not(:first-child) {
-        margin-left: 8px;
-      }
+      line-height: 20px;
+      padding: 5px 12px;
+      color: #fff;
 
-      .svg-icon {
-        font-size: 18px;
+      &:not(:last-child) {
+        margin-right: 8px;
       }
 
-      .svg-icon + span {
-        margin-left: 6px;
-      }
-      > span {
-        display: inline-block;
-        vertical-align: middle;
+      > i {
+        margin-right: 8px;
+        margin-top: -2px;
       }
 
-      &.cursor {
+      &.hover {
         cursor: pointer;
 
         &:hover {
-          background-color: var(--color-background);
+          border-color: #fff;
         }
       }
     }
   }
 }
 
-// menu-dialog
-.menu-dialog {
-  .arco-dialog.is-fullscreen {
-    border-radius: 0;
-    box-shadow: none;
-
-    .arco-dialog__body {
-      padding: 10px;
-
-      &::after {
-        display: none;
-      }
-    }
-  }
-
-  .menu-logout {
-    padding: 10px;
-    width: 52px;
-    height: 52px;
-    margin: 0 auto;
-    border: 1px solid var(--color-text-gray-3);
-    border-radius: 50%;
-    font-size: 30px;
-    text-align: center;
-    color: var(--color-text-gray-3);
-    cursor: pointer;
-
-    &:hover {
-      border-color: var(--color-danger);
-      color: var(--color-danger);
-    }
-  }
-}
-
-// home-breadcrumb
-.home-breadcrumb {
-  margin-bottom: 16px;
-
-  .breadcrumb-tips {
-    display: inline-block;
-    vertical-align: middle;
-    color: var(--color-text-gray);
-
-    > .svg-icon {
-      margin-top: -3px;
-      margin-right: 5px;
-      font-size: 14px;
-    }
-  }
-
-  .arco-breadcrumb {
-    display: inline-block;
-    vertical-align: middle;
-
-    .arco-breadcrumb-item {
-      line-height: 22px;
-      color: var(--color-text-gray);
-    }
-
-    .arco-breadcrumb-item-separator {
-      margin: 0;
-    }
-  }
-}
-
-// home-view
-
-/* view-footer */
 .home-footer {
   position: absolute;
+  left: 0;
+  bottom: 0;
   width: 100%;
   height: 30px;
-  bottom: 0;
-  left: 0;
   z-index: 99;
-  padding: 5px 0;
-  line-height: 20px;
-  color: var(--color-text-gray-3);
-  text-align: center;
-  font-size: 13px;
-  background-color: var(--color-background);
+  padding: 0 10px;
 
-  a {
-    color: var(--color-text-gray-3);
-  }
-
-  a:hover {
-    color: var(--color-text-gray);
-  }
-}
-
-// home-page
-.home-page {
-  width: 1200px;
-  margin: 0 auto;
-
-  .hp-top {
-    display: flex;
-    justify-content: space-between;
-    align-items: stretch;
-
-    &-left {
-      flex-grow: 0;
-      flex-shrink: 0;
-      width: 340px;
-      margin-right: 20px;
-    }
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
 
-    &-right {
-      flex-grow: 2;
+  &-ip {
+    letter-spacing: 1px;
+    > i {
+      margin-right: 5px;
     }
   }
+}
 
-  .tab-summary {
-    margin-bottom: 10px;
-    font-size: 0;
-
-    &-item {
-      position: relative;
-      display: inline-block;
-      width: 100%;
-      vertical-align: top;
-      margin-right: 10px;
-      border-radius: 12px;
-      padding: 16px 20px 16px;
-      color: #fff;
-      text-align: right;
-      background: #3a5ae5;
-
-      &.item-done {
-        background: #9877ff;
-      }
-
-      &.item-wait {
-        cursor: pointer;
-        &:hover {
-          opacity: 0.8;
-        }
-      }
-    }
-
-    &-rp {
-      display: block;
-      position: absolute;
-      width: 10px;
-      height: 10px;
-      background: #fe646a;
-      border: 2px solid #eff0f5;
-      top: -2px;
-      left: -2px;
-      border-radius: 50%;
-      z-index: 9;
-    }
-
-    &-icon {
-      display: block;
-      position: absolute;
-      top: 16px;
-      left: 16px;
-      z-index: auto;
-    }
-
-    &-title {
-      font-size: 12px;
-      margin-bottom: 7px;
-      opacity: 0.6;
-    }
-
-    &-cont {
-      height: 38px;
-      font-size: 32px;
-      font-weight: bold;
-      line-height: 38px;
-    }
+// layout
+.layout {
+  .home-head {
+    background-color: transparent;
   }
 
-  .tab-box {
-    border-radius: 12px;
-    background-color: #fff;
-    margin-bottom: 20px;
-    min-height: 100px;
-    padding: 20px;
-
-    &-title {
-      font-weight: bold;
-      font-size: 16px;
-      line-height: 1;
-      color: #1f222f;
-      margin-bottom: 10px;
-    }
+  .head-logo {
+    position: absolute;
+    width: 140px;
+    height: 24px;
+    top: 48px;
+    left: 48px;
+    background-image: url(../images/bg-logo.png);
+    background-repeat: no-repeat;
+    background-size: 100% 100%;
   }
 
-  .shortcut-list {
+  .head-actions {
     font-size: 0;
-    margin-top: 20px;
+    position: absolute;
+    top: 12px;
+    right: 12px;
   }
-
-  .shortcut-item {
+  .action-icon {
     display: inline-block;
     vertical-align: top;
-    padding: 0 18px;
-    width: 180px;
-    border-radius: 8px;
-    border: 1px solid #eff0f5;
-    height: 52px;
-    line-height: 50px;
-    margin: 10px;
-    text-align: left;
+    width: 32px;
+    height: 32px;
     cursor: pointer;
+    margin-left: 8px;
+    border-radius: 3px;
 
     &:hover {
-      opacity: 0.7;
-    }
-
-    > * {
-      display: inline-block;
-      vertical-align: middle;
-    }
-  }
-
-  .icon {
-    margin-right: 18px;
-  }
-
-  .shortcut-name {
-    font-size: 14px;
-    font-weight: 500;
-    color: #434656;
-  }
-
-  .tab-task {
-    min-height: 190px;
-  }
-  .tab-task-item {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    padding: 10px 0;
-
-    &:not(:last-child) {
-      border-bottom: 1px solid #eff0f5;
-    }
-
-    &-cont {
-      flex-grow: 2;
-      margin: 0 10px;
-      white-space: nowrap;
-      text-overflow: ellipsis;
-      overflow: hidden;
-      line-height: 24px;
-      cursor: pointer;
-
-      &:hover {
-        opacity: 0.7;
-      }
-    }
-
-    &-type {
-      flex-shrink: 0;
-      flex-grow: 0;
-      font-size: 12px;
-      color: rgba(58, 90, 229, 1);
-      border-radius: 6px;
-      text-align: center;
-      width: 36px;
-      height: 24px;
-      line-height: 24px;
-      background: rgba(58, 90, 229, 0.1);
-    }
-  }
-
-  .guide-map {
-    padding: 40px 0;
-    text-align: center;
-  }
-  .guide-item {
-    display: inline-block;
-    vertical-align: middle;
-    position: relative;
-    margin: 0 10px;
-
-    &:not(:last-child):after {
-      content: '';
-      display: block;
-      position: absolute;
-      width: 20px;
-      height: 10px;
-      background-image: url(../images/icon-narrow.png);
-      background-size: 100% 100%;
-      background-repeat: no-repeat;
-
-      top: 50%;
-      right: -20px;
-      margin-top: -5px;
-    }
-  }
-
-  .guide-spin {
-    width: 136px;
-    height: 136px;
-    background: #afb3c7;
-    border-radius: 68px;
-    display: table-cell;
-    vertical-align: middle;
-    text-align: center;
-    color: #1f222f;
-    font-size: 14px;
-    font-weight: bold;
-
-    &.spin-large {
-      width: 192px;
-      height: 192px;
-      border-radius: 96px;
-    }
-
-    &.spin-small {
-      width: 108px;
-      height: 108px;
-      border-radius: 54px;
-    }
-
-    &.spin-light {
-      background: #e1e3eb;
+      background-color: #bedaff;
     }
   }
-  .island-item {
-    width: 282px;
-    background: #afb3c7;
-    border-radius: 20px;
-    padding: 30px;
-    border: 2px solid #fff;
-    position: relative;
+  .action-min {
+    background-image: url(../images/bg-min.png);
+    background-repeat: no-repeat;
+    background-size: 100% 100%;
   }
-  .island-bottom {
-    background: #e1e3eb;
-    &::after {
-      content: '';
-      display: block;
-      position: absolute;
-      width: 40px;
-      border-width: 10px 20px;
-      border-style: solid;
-      border-color: transparent #e1e3eb transparent transparent;
-      top: -10px;
-      right: 50%;
-      z-index: 9;
-    }
-  }
-  .island-top {
-    &::after {
-      content: '';
-      display: block;
-      position: absolute;
-      width: 40px;
-      border-width: 10px 20px;
-      border-style: solid;
-      border-color: transparent transparent transparent #afb3c7;
-      bottom: -10px;
-      left: 50%;
-      z-index: 9;
-    }
-
-    .island-btn {
-      width: 100%;
-    }
-  }
-  .island-title {
-    font-size: 14px;
-    font-weight: bold;
-    color: #1f222f;
-    line-height: 21px;
-    margin-bottom: 20px;
-    text-align: left;
+  .action-close {
+    background-image: url(../images/bg-close.png);
+    background-repeat: no-repeat;
+    background-size: 100% 100%;
   }
-  .island-cont {
-    font-size: 0;
-  }
-  .island-btn {
-    display: inline-block;
-    vertical-align: top;
-    height: 40px;
-    background: #ffffff;
-    border-radius: 6px;
-    font-weight: 500;
-    color: #434656;
-    padding: 14px 20px;
-    font-size: 12px;
-    line-height: 1;
-    text-align: center;
-
-    & + .island-btn {
-      margin-left: 10px;
-    }
+  .action-logout {
+    background-image: url(../images/icon-logout.png);
+    background-size: 70% 70%;
+    background-repeat: no-repeat;
+    background-position: center;
   }
 }

+ 104 - 242
src/assets/styles/pages.less

@@ -1,282 +1,144 @@
 /* login */
 .login-home {
-  position: absolute;
-  top: 0;
-  bottom: 0;
-  left: 0;
-  right: 0;
-  z-index: 8;
-  overflow: auto;
-  background-image: url(assets/images/login-back.png);
-  background-repeat: no-repeat;
-  background-size: cover;
-}
+  .home-body {
+    background-image: url(../images/bg-login.png);
+    background-repeat: no-repeat;
+    background-size: cover;
+    overflow: auto;
+  }
+  .login-action {
+    position: absolute;
+    top: 50px;
+    right: 20px;
+    z-index: auto;
 
-.login-footer {
-  position: absolute;
-  bottom: 0;
-  width: 100%;
-  padding: 10px;
-  color: var(--color-text-gray);
-  text-align: center;
+    &-btn {
+      display: inline-block;
+      vertical-align: top;
+      border: 1px solid #fff;
+      border-radius: 5px;
+      padding: 6px 10px;
+      line-height: 1;
+      color: #fff;
+      margin-left: 10px;
+      cursor: pointer;
 
-  a {
-    margin: 0 5px;
+      > i {
+        margin-right: 5px;
+      }
 
-    &:hover {
-      color: var(--color-primary);
+      &:hover {
+        opacity: 0.8;
+      }
     }
   }
 }
 
-.login-box {
+.login {
   position: absolute;
+  width: 748px;
+  height: 372px;
   top: 50%;
   left: 50%;
-  width: 860px;
-  height: 514px;
   transform: translate(-50%, -50%);
-  overflow: hidden;
-  border-radius: 20px;
-  background-color: #fff;
 }
-.login-theme {
-  width: 420px;
-  height: 100%;
-  border-radius: 20px;
-  background-color: #3858e0;
-  background-image: url(assets/images/login-theme.png);
-  background-size: 100% 100%;
-  float: left;
-  position: relative;
-
-  > h2 {
-    position: absolute;
-    width: 100%;
-    top: 60px;
-    left: 0;
-    text-align: center;
-    font-size: 25px;
-    color: #fff;
-  }
-}
-.login-body {
-  margin-left: 420px;
+.login-spin {
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 320px;
   height: 100%;
-  overflow: hidden;
-  padding: 80px 75px;
-}
-.login-title {
   text-align: center;
-  margin-bottom: 40px;
-  h1 {
-    font-size: 21px;
-    font-weight: bold;
-  }
+  padding-top: 266px;
 
-  img {
-    display: block;
-    max-width: 160px;
-    height: 40px;
-    margin: 0 auto;
-  }
-}
-.login-form {
-  .login-submit-btn {
-    width: 100%;
-    height: 48px;
-    border-radius: 24px;
-    font-size: 18px;
-  }
-}
-
-/* page */
-.page {
-  display: block;
-}
-
-// privilege-set
-.privilege-set {
+  background-image: url(../images/bg-login-spin.png);
+  background-repeat: no-repeat;
+  background-position: 0 20px;
+  background-size: 100% auto;
   overflow: auto;
-  .table {
-    td,
-    th {
-      padding: 8px 10px;
-    }
 
-    th:nth-of-type(4),
-    td:nth-of-type(4) {
-      text-align: center;
-    }
+  > h1 {
+    font-size: 32px;
+    font-weight: 600;
+    line-height: 40px;
+    margin-bottom: 14px;
+    color: var(--color-text-dark);
   }
-  .cell-check-list {
-    text-align: left;
+  > h4 {
+    font-weight: 400;
+    font-size: 20px;
+    color: #8c8c8c;
+    line-height: 28px;
+    font-style: normal;
   }
 }
 
-// label-edit
-.label-edit {
-  min-height: 60px;
-  .label-item {
-    display: inline-block;
-    vertical-align: top;
-    border: 1px solid var(--color-border);
-    border-radius: 3px;
-    padding: 4px 40px 5px 10px;
-    position: relative;
-    margin: 0 10px 10px 0;
-    line-height: 20px;
-  }
-  .label-item-content {
-    margin: 0;
-    line-height: 20px;
-    vertical-align: middle;
-  }
-  .label-item-delete {
-    position: absolute;
-    right: 10px;
-    top: 50%;
-    transform: translateY(-50%);
-    z-index: 99;
-    font-size: 16px;
-    color: var(--color-text-gray-5);
-    cursor: pointer;
-    &:hover {
-      color: var(--color-danger);
-    }
-  }
-  .label-add {
-    display: inline-block;
-    vertical-align: top;
-    border: 1px solid var(--color-border);
-    border-radius: 3px;
-    padding: 0 10px;
-    color: var(--color-text-gray-2);
-    line-height: 31px;
-    cursor: pointer;
-    &:hover {
-      border-color: var(--color-primary);
-      color: var(--color-primary);
-    }
-  }
-}
-// field-transfer
-.field-transfer {
-  display: flex;
-  align-items: stretch;
-  .field-part-source {
-    width: 200px;
-    border-radius: 10px;
-    border: 1px solid var(--color-border-bold);
-    overflow: hidden;
-  }
-  .field-part-action {
-    width: 120px;
+.login-body {
+  position: absolute;
+  right: 0;
+  top: 0;
+  padding: 32px;
+  width: 344px;
+  height: 372px;
+  background: #ffffff;
+  box-shadow: 0px 2px 12px 0px rgba(0, 0, 0, 0.08);
+  border-radius: 8px;
+  border: 1px solid #e5e6eb;
+
+  .login-head {
     display: flex;
-    justify-content: center;
-    align-items: center;
-
-    .action-body {
-      width: 80px;
-      height: 100px;
-      line-height: 50px;
-      text-align: center;
+    justify-content: space-between;
+    margin-bottom: 28px;
+
+    &-title {
+      font-weight: bold;
+      font-size: 24px;
+      color: #262626;
+      line-height: 32px;
     }
-
-    .arco-btn {
-      margin: 0;
+    &-subtitle {
+      font-weight: 400;
+      font-size: 16px;
+      color: #8c8c8c;
+      line-height: 24px;
+      margin-top: 4px;
     }
   }
-  .field-title {
-    background-color: var(--color-background);
-    padding: 10px 15px;
-    line-height: 20px;
-    border-bottom: 1px solid var(--color-border-bold);
-  }
-  .field-body {
-    padding: 6px 15px;
-    overflow-x: hidden;
-    overflow-y: auto;
-    height: 246px;
-  }
-  .field-item {
-    position: relative;
-    padding: 5px 0;
-    line-height: 20px;
 
-    &.after-drop {
-      &::after {
-        content: '';
-        display: block;
-        position: absolute;
-        width: 100%;
-        border-bottom: 2px solid var(--color-primary);
-        bottom: -2px;
-        left: 0;
-        z-index: 9;
-      }
-    }
-    &.before-drop {
-      &::before {
-        content: '';
-        display: block;
-        position: absolute;
-        width: 100%;
-        border-top: 2px solid var(--color-primary);
-        top: -2px;
-        left: 0;
-        z-index: 9;
-      }
-    }
-  }
-}
-
-// card-title-rule-edit
-.card-title-rule-edit {
-  .field-item {
-    display: inline-block;
-    vertical-align: top;
-    margin: 0 10px 10px 0;
-    padding: 8px 10px;
+  .login-btn {
+    height: 32px;
+    padding: 5px 16px;
     border-radius: 4px;
-    line-height: 1;
-    background-color: var(--color-background);
-    cursor: pointer;
+    line-height: 20px;
+    text-align: center;
 
     &:hover {
       color: var(--color-primary);
+      border-color: var(--color-primary);
     }
-    &.is-act {
-      background-color: var(--color-primary);
-      color: #fff !important;
+  }
+  .btn-setting {
+    &::before {
+      content: '';
+      display: inline-block;
+      vertical-align: middle;
+      margin-right: 3px;
+      margin-top: -2px;
+      width: 16px;
+      height: 16px;
+      background-image: url(../images/icon-setting.png);
+      background-size: 100% 100%;
     }
-    &.is-disabled {
-      cursor: not-allowed;
 
-      &:hover {
-        color: var(--color-text-dark);
+    &:hover {
+      &::before {
+        background-image: url(../images/icon-setting-act.png);
       }
     }
   }
-  .field-textarea {
-    border-radius: var(--border-radius);
-    border: 1px solid var(--color-text-gray-4);
-    min-height: 60px;
-    padding: 2px;
-    overflow: hidden;
-
-    &:focus {
-      border-color: var(--color-primary);
-    }
-
-    span.var-field {
-      display: inline-block;
-      vertical-align: middle;
-      padding: 3px 5px;
-      background-color: var(--color-primary);
-      color: var(--color-white);
-      line-height: 1;
-      border-radius: 3px;
-    }
+  .login-submit {
+    width: 100%;
+    border-radius: 4px;
+    margin-top: 4px;
   }
 }

+ 48 - 4
src/assets/styles/var.less

@@ -10,20 +10,23 @@ body {
   --color-border: #e5e5e5;
   --color-border-bold: #d5d5d5;
   --color-background: #f2f3f5;
+  --form-color-border: #d9d9d9;
 
   /* status */
   --color-primary: #165dff;
   --color-primary-light: #e8f3ff;
   --color-success: #00b42a;
-  --color-success-light: #e8ffea;
+  --color-success-light: #32cf8a;
   --color-warning: #ff9427;
-  --color-warning-light: #fff7e8;
-  --color-danger: #f53f3f;
-  --color-danger-light: #ffece8;
+  --color-danger: #fe5d4e;
   --color-cyan: #2abcff;
   --color-cyan-light: #5fc9fa;
+  --color-blue: #3491fa;
+  --color-blue-dark: #172666;
+  --color-purple: #9877ff;
   --color-white: #fff;
   --color-dark: #262626;
+  --color-transparent: transparent;
 
   /* shadow */
   --shadow-light: 0 0 1px rgba(0 0 0 0.15);
@@ -44,3 +47,44 @@ body {
   --border-radius-small: 4px;
   --color-text-1: --color-text-dark-1;
 }
+
+// 自定义var
+@input-color-border: var(--form-color-border);
+@input-color-border_disabled: var(--form-color-border);
+@input-color-border_hover: var(--form-color-border);
+@input-color-border_error: var(--form-color-border);
+@input-color-border_error_hover: var(--form-color-border);
+
+@input-color-bg: var(--color-transparent);
+@input-color-bg_hover: var(--color-transparent);
+@input-color-bg_focus: var(--color-transparent);
+
+@input-color-bg_error: var(--color-transparent);
+@input-color-bg_error_hover: var(--color-transparent);
+
+@form-color-bg_error: var(--color-transparent);
+@form-color-bg_error_hover: var(--color-transparent);
+@form-color-bg_error_focus: var(--color-transparent);
+@form-color-bg_warning: var(--color-transparent);
+@form-color-bg_warning_hover: var(--color-transparent);
+@form-color-bg_warning_focus: var(--color-transparent);
+@form-color-bg_success: var(--color-transparent);
+@form-color-bg_success_hover: var(--color-transparent);
+@form-color-bg_success_focus: var(--color-transparent);
+
+@form-color-border_validating: var(--form-color-border);
+@form-color-border_validating_hover: var(--color-primary-6);
+@form-color-border_error: rgb(var(--danger-6));
+@form-color-border_error_hover: rgb(var(--danger-6));
+@form-color-border_success: rgb(var(--success-6));
+@form-color-border_success_hover: rgb(var(--success-6));
+@form-color-border_warning: rgb(var(--warning-6));
+@form-color-border_warning_hover: rgb(var(--warning-6));
+
+@picker-color-border: var(--form-color-border);
+@picker-color-border_hover: var(--form-color-border);
+@picker-color-border_disabled: var(--color-border);
+@picker-color-border_error: rgb(var(--danger-6));
+@picker-color-border_error_hover: rgb(var(--danger-6));
+@picker-color-bg: var(--color-transparent);
+@picker-color-bg_hover: var(--color-transparent);

+ 0 - 45
src/hooks/permission.ts

@@ -1,45 +0,0 @@
-import { useAppStore } from '@/store';
-import { reactive } from 'vue';
-import { useRoute } from 'vue-router';
-
-export default function usePermission() {
-  const appStore = useAppStore();
-  const route = useRoute();
-  const permissions = reactive<Record<string, boolean>>({});
-
-  function checkPrivilege(type: string, field: string, routeAlias = '') {
-    const routerName = routeAlias || (route.name as string);
-    const keys = field
-      .split(',')
-      .map((item) => `${type}_${item}`.toLowerCase());
-
-    return keys.some((key) => appStore.privilegeMap[routerName].includes(key));
-  }
-
-  function updatePermission() {
-    const validKeys = appStore.privilegeMap[route.name as string];
-
-    validKeys.forEach((key: string) => {
-      permissions[key] = true;
-    });
-  }
-  updatePermission();
-
-  return {
-    checkPrivilege,
-    updatePermission,
-    permissions,
-  };
-}
-
-/*
-v-if="permissions.condition_condition"
-v-if="permissions.button_select"
-v-if="permissions.button_add"
-v-if="permissions.button_export"
-v-if="permissions.button_import"
-v-if="permissions.link_preview"
-v-if="permissions.link_edit"
-v-if="permissions.link_delete"
-v-if="permissions.link_enable"
- */

+ 15 - 284
src/layout/default-layout.vue

@@ -1,300 +1,31 @@
 <template>
-  <div :class="['home', { 'home-page-main': IS_HOME_PAGE }]">
-    <div class="home-header">
-      <div class="head-logo">
-        <div class="head-logo-content">
-          <img
-            v-if="userStore.curSchoolInfo.logo"
-            :src="userStore.curSchoolInfo.logo"
-            alt="知学知考"
-          />
-          <h1 v-else>知学知考</h1>
-        </div>
-      </div>
-      <div class="home-menu">
-        <div
-          v-for="(menu, index) in appStore.appMenus"
-          :key="index"
-          :class="['home-menu-item', { 'is-active': curMenu.url === menu.url }]"
-          @click="toMenu(menu)"
-        >
-          <span>{{ menu.name }}</span>
-        </div>
-      </div>
-
-      <div class="home-action">
-        <a-tooltip
-          :content="hasMoreSchool ? '切换学校' : '学校名称'"
-          position="br"
-        >
-          <div class="home-action-item cursor" @click="toSelectSchool">
-            <svg-icon name="icon-home" fill="#BFBFBF" />
-            <span :title="userStore.realName">{{
-              userStore.curSchoolInfo.name
-            }}</span>
-          </div>
-        </a-tooltip>
-        <a-tooltip content="修改密码" position="br">
-          <div class="home-action-item cursor" @click="toResetPwd">
-            <svg-icon name="icon-user" fill="#BFBFBF" />
-            <span :title="userStore.realName">{{ userStore.realName }}</span>
-          </div>
-        </a-tooltip>
-        <a-tooltip content="退出登录" position="br">
-          <div class="home-action-item cursor" @click="toLogout">
-            <svg-icon name="icon-logout" />
-          </div>
-        </a-tooltip>
-      </div>
+  <div class="home layout">
+    <div class="home-head">
+      <div class="head-logo"></div>
     </div>
-
-    <div v-if="!IS_HOME_PAGE" class="home-navs">
-      <a-menu
-        v-if="curMenu.children && curMenu.children.length"
-        class="arco-menu-home"
-        :default-selected-keys="[curRouteName]"
-        :default-open-keys="curSubMenuNames"
-        @menu-item-click="toMenuItem"
-      >
-        <template v-for="submenu in curMenu.children">
-          <a-sub-menu
-            v-if="submenu.children && submenu.children.length"
-            :key="submenu.url"
-          >
-            <template #title>
-              <span>{{ submenu.name }}</span>
-            </template>
-
-            <a-menu-item v-for="nav in submenu.children" :key="nav.url">
-              <span>{{ nav.name }}</span>
-              <!-- <span
-                v-if="nav.url === 'WaitTask' && waitTaskCount"
-                class="nav-item-info"
-              >
-                {{ waitTaskCount }}
-              </span> -->
-            </a-menu-item>
-          </a-sub-menu>
-          <a-menu-item v-else :key="submenu.url">
-            <span>{{ submenu.name }}</span>
-          </a-menu-item>
-        </template>
-      </a-menu>
-    </div>
-
     <div class="home-body">
-      <div class="home-body-content">
-        <div v-if="breadcrumbs.length" class="home-breadcrumb">
-          <span class="breadcrumb-tips">
-            <svg-icon name="icon-home" />
-            <span>当前所在位置:</span>
-          </span>
-          <a-breadcrumb>
-            <a-breadcrumb-item v-for="bread in breadcrumbs" :key="bread.url">
-              {{ bread.name }}
-            </a-breadcrumb-item>
-          </a-breadcrumb>
-        </div>
-
-        <!-- home-view: page detail -->
-        <div class="home-view">
-          <router-view />
-        </div>
-      </div>
+      <router-view />
     </div>
+    <div v-if="props.showFooter" class="home-footer">
+      <div class="home-footer-ip">
+        <i class="el-icon-monitor"></i><span>{{ appStore.domain }}</span>
+      </div>
 
-    <Footer />
+      <div class="home-footer-version">版本号:{{ appStore.version }}</div>
+    </div>
   </div>
-
-  <!-- ResetPwd -->
-  <ResetPwd
-    ref="ResetPwdRef"
-    :user-info="userInfo"
-    @modified="resetPwdModified"
-  />
 </template>
 
 <script lang="ts" setup>
-  import { computed, onMounted, ref, watch } from 'vue';
-  import { useRoute, useRouter } from 'vue-router';
-  import { useAppStore, useUserStore } from '@/store';
-  import { SYS_ADMIN_NAME } from '@/constants/enumerate';
-  import { HOME_PAGE_ROUTE } from '@/router/constants';
-  import { modalConfirm } from '@/utils/arco';
-
-  import ResetPwd from '@/views/login/login/ResetPwd.vue';
-  import Footer from '@/components/footer/index.vue';
-
-  import type { AppMenuItem, PrivilegeItem } from '@/store/modules/app/types';
+  import { useAppStore } from '@/store';
 
   defineOptions({
     name: 'DefaultLayout',
   });
 
-  const appStore = useAppStore();
-  const userStore = useUserStore();
-  const route = useRoute();
-  const router = useRouter();
-
-  const curMenu = ref({} as AppMenuItem);
-  const curRouteName = ref('');
-  const curSubMenuNames = ref<string[]>([]);
-  const breadcrumbs = ref<PrivilegeItem[]>([]);
-  const userInfo = ref({
-    userId: userStore.id,
-    loginName: '',
-    schoolCode: '',
-    password: '',
-    mobileNumber: '1',
-    pwdCount: 0,
-    phoneLogin: false,
-  });
-  const ResetPwdRef = ref();
-
-  const IS_SUPER_ADMIN = computed(() => {
-    return userStore.loginName === SYS_ADMIN_NAME;
-  });
-  const IS_HOME_PAGE = computed(() => {
-    return route.name === HOME_PAGE_ROUTE;
-  });
-  const hasMoreSchool = computed(() => {
-    return userStore.schoolInfo?.length > 1;
-  });
+  const props = defineProps<{
+    showFooter?: boolean;
+  }>();
 
-  function initData() {
-    if (IS_HOME_PAGE.value) {
-      curMenu.value = {
-        id: '',
-        name: '首页',
-        type: '',
-        url: HOME_PAGE_ROUTE,
-        parentId: '',
-      };
-      return;
-    }
-
-    updateBreadcrumbs();
-    const curUrl = breadcrumbs.value[0]?.url;
-    const currentMenu = appStore.appMenus.find((menu) => menu.url === curUrl);
-    if (!currentMenu) return;
-    menuChange(currentMenu);
-
-    // 待办任务数量展示
-    if (curMenu.value.url === 'exam') {
-      // updateWaitTaskCount();
-    }
-  }
-
-  function toMenu(menu: AppMenuItem) {
-    if (menu.url === HOME_PAGE_ROUTE) {
-      if (route.name !== HOME_PAGE_ROUTE) {
-        curMenu.value = menu;
-        router.push({
-          name: HOME_PAGE_ROUTE,
-        });
-      }
-      return;
-    }
-    if (curMenu.value.url === menu.url) return;
-    menuChange(menu);
-    const firstRouteName = getCurMenuFirstRouter();
-    router.push({
-      name: firstRouteName,
-    });
-  }
-
-  function getCurMenuFirstRouter() {
-    let firstRouteName = '';
-    let menu: AppMenuItem | undefined = curMenu.value;
-    while (menu) {
-      firstRouteName = menu.url;
-      if (menu.children) {
-        menu = menu.children[0];
-      } else {
-        menu = undefined;
-      }
-    }
-
-    return firstRouteName;
-  }
-
-  function updateBreadcrumbs() {
-    curRouteName.value = route.name as string;
-    const breadcrumbsData: PrivilegeItem[] = [];
-
-    let curBreadcrumb = appStore.privilegeList.find(
-      (item) => item.url === curRouteName.value
-    );
-    if (curBreadcrumb) breadcrumbsData.push({ ...curBreadcrumb });
-
-    while (curBreadcrumb && curBreadcrumb.parentId !== '-1') {
-      const pid = curBreadcrumb.parentId;
-      curBreadcrumb = appStore.privilegeList.find((item) => item.id === pid);
-      if (curBreadcrumb) breadcrumbsData.unshift({ ...curBreadcrumb });
-    }
-    breadcrumbs.value = breadcrumbsData;
-  }
-
-  function menuChange(menu: AppMenuItem) {
-    curMenu.value = menu;
-    curSubMenuNames.value = appStore.privilegeList
-      .filter((item) => item.parentId === menu.id)
-      .map((item) => item.url);
-  }
-
-  function routerChange() {
-    updateBreadcrumbs();
-
-    const curUrl = breadcrumbs.value[0].url;
-    const currentMenu = appStore.appMenus.find((menu) => menu.url === curUrl);
-    if (!currentMenu) return;
-    menuChange(currentMenu);
-
-    if (curMenu.value.url === 'exam') {
-      // updateWaitTaskCount();
-    }
-  }
-
-  function toMenuItem(val: string) {
-    router.push({ name: val });
-  }
-
-  function toSelectSchool() {
-    if (IS_SUPER_ADMIN.value) {
-      router.push({ name: 'SelectSchool' });
-      return;
-    }
-    if (hasMoreSchool.value) {
-      // this.$refs.SwitchSchoolDialog.open();
-    }
-  }
-
-  function toResetPwd() {
-    if (IS_SUPER_ADMIN.value) return;
-    ResetPwdRef.value?.open();
-  }
-
-  function resetPwdModified() {
-    userStore.logout();
-  }
-
-  async function toLogout() {
-    const confirmRes = await modalConfirm('提示', `确定要退出登录吗?`).catch(
-      () => false
-    );
-    if (confirmRes !== 'confirm') return;
-    userStore.logout();
-  }
-
-  onMounted(() => {
-    initData();
-  });
-
-  watch(
-    () => route.name,
-    (val) => {
-      if (val === HOME_PAGE_ROUTE) return;
-      routerChange();
-    }
-  );
+  const appStore = useAppStore();
 </script>

+ 1 - 0
src/router/constants.ts

@@ -9,6 +9,7 @@ export const DEFAULT_ROUTE_NAME = 'Workplace';
 export const WHITE_LIST = [
   NOT_FOUND,
   { name: 'Login' },
+  { name: 'Setting' },
   { name: 'TestPage' },
   { name: HOME_PAGE_ROUTE },
 ];

+ 0 - 2
src/router/guard/index.ts

@@ -1,7 +1,6 @@
 import type { Router } from 'vue-router';
 import { setRouteEmitter } from '@/utils/route-listener';
 import setupUserLoginInfoGuard from './userLoginInfo';
-import setupPermissionGuard from './permission';
 
 function setupPageGuard(router: Router) {
   router.beforeEach(async (to) => {
@@ -13,5 +12,4 @@ function setupPageGuard(router: Router) {
 export default function createRouteGuard(router: Router) {
   setupPageGuard(router);
   setupUserLoginInfoGuard(router);
-  setupPermissionGuard(router);
 }

+ 0 - 37
src/router/guard/permission.ts

@@ -1,37 +0,0 @@
-import type { Router } from 'vue-router';
-import NProgress from 'nprogress'; // progress bar
-
-import { useAppStore } from '@/store';
-import { WHITE_LIST, NOT_FOUND, DEFAULT_ROUTE_NAME } from '../constants';
-
-export default function setupUserLoginInfoGuard(router: Router) {
-  router.beforeEach(async (to, from, next) => {
-    if (WHITE_LIST.find((el) => el.name === to.name)) {
-      next();
-      NProgress.done();
-      return;
-    }
-
-    const appStore = useAppStore();
-    if (!appStore.appMenus.length) {
-      await appStore.fetchServerMenu();
-    }
-
-    if (to.name === DEFAULT_ROUTE_NAME) {
-      const destination = appStore.getMenuFirstRouter() || NOT_FOUND;
-      console.log(destination);
-
-      next(destination);
-      NProgress.done();
-      return;
-    }
-
-    if (!appStore.validRoutes.includes(to.name as string)) {
-      next(NOT_FOUND);
-    } else {
-      next();
-    }
-
-    NProgress.done();
-  });
-}

+ 1 - 1
src/router/index.ts

@@ -13,7 +13,7 @@ const router = createRouter({
   routes: [
     {
       path: '/',
-      redirect: { name: 'TestPage' },
+      redirect: { name: 'Login' },
     },
     ...appRoutes,
     ...appExternalRoutes,

+ 9 - 0
src/router/routes/modules/login.ts

@@ -13,6 +13,15 @@ const LOGIN: AppRouteRecordRaw = {
         requiresAuth: false,
       },
     },
+    {
+      path: 'setting',
+      name: 'Setting',
+      component: () => import('@/views/login/setting/index.vue'),
+      meta: {
+        title: '设置',
+        requiresAuth: false,
+      },
+    },
     {
       path: 'test-page',
       name: 'TestPage',

+ 2 - 98
src/store/modules/app/index.ts

@@ -1,80 +1,10 @@
 import { defineStore } from 'pinia';
-import { sysMenu } from '@/api/user';
-import staticMenu from '@/constants/staticMenu';
-import { HOME_PAGE_ROUTE } from '@/router/constants';
-
-import type { UserMenuItem, UserMenuPrivilegeEnum } from '@/api/types/user';
-import { AppState, PrivilegeItem, AppMenuItem } from './types';
-
-function initPrivilegeMap(data: UserMenuItem[]) {
-  const privilegeMap: Record<string, string[]> = {};
-  const pageSetTypes: UserMenuPrivilegeEnum[] = [
-    'conditions',
-    'buttons',
-    'lists',
-    'links',
-  ];
-  data.forEach((item) => {
-    privilegeMap[item.url] = [item.id];
-    pageSetTypes.forEach((type) => {
-      if (item[type]) return;
-
-      (item[type] as UserMenuItem[]).forEach((elem) => {
-        privilegeMap[item.url].push(`${elem.type}_${elem.url}`.toLowerCase());
-      });
-    });
-  });
-  return privilegeMap;
-}
-
-function transformMenu(list: UserMenuItem[]): PrivilegeItem[] {
-  return list.map((item) => {
-    return {
-      id: item.id,
-      parentId: item.parentId,
-      name: item.name,
-      type: item.type,
-      url: item.url,
-    };
-  });
-}
-
-function getMenu(privilegeData: PrivilegeItem[]): {
-  menuList: AppMenuItem[];
-  validRoutes: string[];
-} {
-  const getChildren = (id: string) => {
-    return privilegeData
-      .filter((item) => item.parentId === id)
-      .map((item) => {
-        return { ...item } as AppMenuItem;
-      });
-  };
-  const menuList = getChildren('-1');
-  const validRoutes: string[] = [];
-  const toTree = (data: AppMenuItem[]) => {
-    data.forEach((menu) => {
-      const children = getChildren(menu.id);
-      if (children.length) {
-        menu.children = children;
-        toTree(menu.children);
-      } else {
-        validRoutes.push(menu.url);
-      }
-    });
-  };
-  toTree(menuList);
-
-  return { menuList, validRoutes };
-}
+import { AppState } from './types';
 
 const useAppStore = defineStore('app', {
   state: (): AppState => ({
     version: '',
-    privilegeMap: {},
-    appMenus: [],
-    privilegeList: [],
-    validRoutes: [],
+    domain: '',
     device: 'desktop',
   }),
 
@@ -91,32 +21,6 @@ const useAppStore = defineStore('app', {
     setInfo(partial: Partial<AppState>) {
       this.$patch(partial);
     },
-    async fetchServerMenu() {
-      const res = await sysMenu().catch(() => {});
-      if (!res) return;
-      const privilegeList: UserMenuItem[] = [...staticMenu, ...res.privileges];
-      this.privilegeMap = initPrivilegeMap(privilegeList);
-      this.privilegeList = transformMenu(privilegeList);
-      const { menuList, validRoutes } = getMenu(this.privilegeList);
-      this.appMenus = menuList;
-      this.validRoutes = validRoutes;
-    },
-    getMenuFirstRouter() {
-      let firstRouteName = '';
-      let menu: AppMenuItem | undefined = this.appMenus[0];
-      if (menu.url === HOME_PAGE_ROUTE) return { name: HOME_PAGE_ROUTE };
-
-      while (menu) {
-        firstRouteName = menu.url;
-        if (menu.children) {
-          menu = menu.children[0];
-        } else {
-          menu = undefined;
-        }
-      }
-
-      return { name: firstRouteName };
-    },
     toggleDevice(device: string) {
       this.device = device;
     },

+ 1 - 15
src/store/modules/app/types.ts

@@ -1,19 +1,5 @@
-import type { UserMenuItem } from '@/api/types/user';
-
-export type PrivilegeItem = Pick<
-  UserMenuItem,
-  'id' | 'name' | 'type' | 'url' | 'parentId'
->;
-
-export interface AppMenuItem extends PrivilegeItem {
-  children?: AppMenuItem[];
-}
-
 export interface AppState {
   version: string;
-  privilegeMap: Record<string, string[]>;
-  appMenus: AppMenuItem[];
-  privilegeList: PrivilegeItem[];
-  validRoutes: string[];
+  domain: string;
   device: string;
 }

+ 5 - 0
src/types/global.ts

@@ -44,3 +44,8 @@ export interface SelectOptions {
   value: string | number | boolean | Record<string, unknown>;
   label: string;
 }
+
+export interface OptionListItem {
+  value: string;
+  label: string;
+}

+ 22 - 5
src/views/login/home.vue

@@ -1,10 +1,27 @@
 <template>
-  <div class="login-home">
-    <router-view></router-view>
-    <Footer class="login-footer" />
+  <div class="home layout login-home">
+    <div class="home-head">
+      <div class="head-logo"></div>
+    </div>
+    <div class="home-body">
+      <router-view />
+    </div>
+    <div class="home-footer">
+      <div class="home-footer-ip">
+        <i class="el-icon-monitor"></i><span>{{ appStore.domain }}</span>
+      </div>
+
+      <div class="home-footer-version">版本号:{{ appStore.version }}</div>
+    </div>
   </div>
 </template>
 
-<script setup lang="ts" name="LoginHome">
-  import Footer from '@/components/footer/index.vue';
+<script lang="ts" setup>
+  import { useAppStore } from '@/store';
+
+  defineOptions({
+    name: 'LoginHome',
+  });
+
+  const appStore = useAppStore();
 </script>

+ 0 - 246
src/views/login/login/ResetPwd.vue

@@ -1,246 +0,0 @@
-<template>
-  <a-modal v-model:visible="visible">
-    <template #title> {{ title }} </template>
-    <a-form ref="formRef" :model="formData" :rules="rules">
-      <!-- reset pwd -->
-      <template v-if="needResetPwd">
-        <a-form-item field="oldPassword" label="旧密码" auto-label-width>
-          <a-input-password
-            v-model.trim="formData.oldPassword"
-            placeholder="请输入旧密码"
-            allow-clear
-          >
-            <template #prefix>
-              <icon-lock />
-            </template>
-          </a-input-password>
-        </a-form-item>
-        <a-form-item field="password" label="新密码">
-          <a-input-password
-            v-model.trim="formData.password"
-            placeholder="请输入新密码"
-            allow-clear
-          >
-            <template #prefix>
-              <icon-lock />
-            </template>
-          </a-input-password>
-        </a-form-item>
-        <a-form-item field="rePassword" label="再次密码">
-          <a-input-password
-            v-model.trim="formData.rePassword"
-            placeholder="请再次输入新密码"
-            allow-clear
-          >
-            <template #prefix>
-              <icon-lock />
-            </template>
-          </a-input-password>
-        </a-form-item>
-      </template>
-      <!-- bind mobile -->
-      <template v-if="needBindMobile">
-        <a-form-item field="mobileNumber" label="手机号">
-          <a-input
-            v-model.trim="formData.mobileNumber"
-            placeholder="请输入手机号"
-            allow-clear
-          >
-            <template #prefix> <icon-mobile /> </template>
-          </a-input>
-        </a-form-item>
-        <a-form-item field="code" label="验证码">
-          <div class="box-justify">
-            <a-input
-              v-model.trim="formData.code"
-              placeholder="请输入手机验证码"
-              allow-clear
-            >
-              <template #prefix>
-                <icon-message />
-              </template>
-            </a-input>
-            <a-button
-              type="primary"
-              :disabled="isFetchingCode"
-              @click="fetchMobileSmsCode"
-              >{{ codeContent }}</a-button
-            >
-          </div>
-        </a-form-item>
-      </template>
-    </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 { computed, reactive, ref } from 'vue';
-  import type { FormInstance, FieldRule } from '@arco-design/web-vue/es/form';
-  import { smscode, phone, password, strictPassword } from '@/utils/formRules';
-  import useLoading from '@/hooks/loading';
-  import useSms from '@/hooks/sms';
-  import { getSmsCodeForBind, updatePwd } from '@/api/user';
-  import { Message } from '@arco-design/web-vue';
-  import { getBase64 } from '@/utils/crypto';
-  import { UpdatePwdData } from '@/api/types/user';
-  import { FormRules } from '@/types/global';
-  import { ResetPwdUserInfo } from './types';
-
-  defineOptions({
-    name: 'ResetPwd',
-  });
-
-  const defaultFormData = {
-    id: '',
-    oldPassword: '',
-    password: '',
-    rePassword: '',
-    mobileNumber: '',
-    code: '',
-  };
-  type FormDataType = typeof defaultFormData;
-
-  const props = defineProps<{
-    userInfo: ResetPwdUserInfo;
-  }>();
-
-  const emit = defineEmits(['modified']);
-
-  const needBindMobile = computed(() => {
-    return !props.userInfo.mobileNumber && props.userInfo.phoneLogin;
-  });
-  const needResetPwd = computed(() => {
-    return !props.userInfo.pwdCount;
-  });
-  const title = computed(() => {
-    if (needBindMobile.value && needResetPwd.value)
-      return '修改密码与绑定手机号';
-    if (needBindMobile.value) return '绑定手机号';
-    if (needResetPwd.value) return '修改密码';
-
-    return '修改密码';
-  });
-
-  const visible = ref(false);
-  const formRef = ref<FormInstance>();
-  const formData = reactive<FormDataType>({ ...defaultFormData });
-  const passwordRule: FieldRule[] = [
-    ...strictPassword,
-    {
-      validator: (value, callback) => {
-        if (value === props.userInfo.loginName) {
-          return callback('禁止使用用户账户号作为密码');
-        }
-        return callback();
-      },
-    },
-  ];
-  const rules: FormRules<keyof FormDataType> = {
-    code: smscode,
-    mobileNumber: phone,
-    oldPassword: password,
-    password: [
-      ...passwordRule,
-      {
-        validator: (value, callback) => {
-          if (value === formData.oldPassword) {
-            callback('新旧密码不可以相同');
-          } else {
-            callback();
-          }
-        },
-      },
-    ],
-    rePassword: [
-      ...passwordRule,
-      {
-        validator: (value, callback) => {
-          if (value !== formData.password) {
-            callback('两次输入的密码不一致');
-          } else {
-            callback();
-          }
-        },
-      },
-    ],
-  };
-
-  /* sms  ----start */
-  const { isFetchingCode, codeContent, changeContent } = useSms('login');
-
-  async function fetchMobileSmsCode() {
-    const unvalid = await formRef.value?.validateField([
-      'mobileNumber',
-      'schoolCode',
-    ]);
-    if (unvalid) return;
-
-    isFetchingCode.value = true;
-    const res = await getSmsCodeForBind({
-      schoolCode: props.userInfo.schoolCode,
-      loginName: props.userInfo.loginName,
-      mobileNumber: formData.mobileNumber,
-      password: getBase64(formData.oldPassword),
-    }).catch(() => {
-      isFetchingCode.value = false;
-    });
-    if (!res) return;
-
-    Message.success(
-      `已向手机尾号【${formData.mobileNumber.slice(
-        -4
-      )}】成功发送短信,请在2分钟内进行验证!`
-    );
-    changeContent();
-  }
-  /* sms  ----end */
-
-  /* confirm */
-  const { loading, setLoading } = useLoading();
-  async function confirm() {
-    const err = await formRef.value?.validate();
-    if (err) return;
-
-    setLoading(true);
-    let datas = {
-      id: formData.id,
-      oldPassword: getBase64(formData.oldPassword),
-    } as UpdatePwdData;
-
-    if (needBindMobile.value) {
-      datas = {
-        ...datas,
-        mobileNumber: formData.mobileNumber,
-        verifyCode: formData.code,
-      };
-    }
-    if (needResetPwd.value) {
-      datas = {
-        ...datas,
-        password: getBase64(formData.password),
-      };
-    }
-    const res = await updatePwd(datas).catch(() => {});
-    setLoading(false);
-    if (!res) return;
-    Message.success('修改成功!');
-    emit('modified', res);
-    close();
-  }
-
-  /* modal */
-  function open() {
-    visible.value = true;
-  }
-  function close() {
-    visible.value = false;
-  }
-  defineExpose({ open, close });
-</script>

+ 93 - 288
src/views/login/login/index.vue

@@ -1,165 +1,94 @@
 <template>
-  <div class="login login-box">
-    <div class="login-theme"><h2>知学知考</h2></div>
-    <div class="login-body" @keyup.enter="submit">
-      <div class="login-title">
-        <img v-if="schoolInfo.logo" :src="schoolInfo.logo" alt="学校logo" />
-        <h1 v-else>知学知考</h1>
-      </div>
-      <div class="login-form">
-        <a-form
-          ref="formRef"
-          :model="formData"
-          :rules="rules"
-          :label-col-props="{ span: 0, offset: 0 }"
-          :wrapper-col-props="{ span: 24, offset: 0 }"
-        >
-          <!-- account -->
-          <template v-if="IS_USERNAME_TYPE">
-            <a-form-item field="loginName">
-              <a-input
-                v-model.trim="formData.loginName"
-                placeholder="请输入账号"
-                allow-clear
-              >
-                <template #prefix>
-                  <icon-user />
-                </template>
-              </a-input>
-            </a-form-item>
-            <a-form-item field="password">
-              <a-input-password
-                v-model.trim="formData.password"
-                placeholder="请输入密码"
-                allow-clear
-              >
-                <template #prefix>
-                  <icon-lock />
-                </template>
-              </a-input-password>
-            </a-form-item>
-            <a-form-item v-if="schoolInfo.accountSmsVerify" field="code">
-              <div class="box-justify">
-                <a-input
-                  v-model.trim="formData.code"
-                  placeholder="请输入手机验证码"
-                  allow-clear
-                >
-                  <template #prefix>
-                    <icon-message />
-                  </template>
-                </a-input>
-                <a-button
-                  type="primary"
-                  :disabled="isFetchingCode"
-                  @click="fetchAccountSmsCode"
-                  >{{ codeContent }}</a-button
-                >
-              </div>
-            </a-form-item>
-          </template>
-          <!-- mobile -->
-          <template v-else>
-            <a-form-item field="mobileNumber">
-              <a-input
-                v-model.trim="formData.mobileNumber"
-                placeholder="请输入手机号"
-                allow-clear
-              >
-                <template #prefix> <icon-mobile /> </template>
-              </a-input>
-            </a-form-item>
-            <a-form-item field="code">
-              <div class="box-justify">
-                <a-input
-                  v-model.trim="formData.code"
-                  placeholder="请输入手机验证码"
-                  allow-clear
-                >
-                  <template #prefix>
-                    <icon-message />
-                  </template>
-                </a-input>
-                <a-button
-                  type="primary"
-                  :disabled="isFetchingCode"
-                  @click="fetchMobileSmsCode"
-                  >{{ codeContent }}</a-button
-                >
-              </div>
-            </a-form-item>
-          </template>
-          <a-form-item>
-            <a-button type="primary" :loading="loading" long @click="submit"
-              >登录</a-button
-            >
-          </a-form-item>
-        </a-form>
-      </div>
-      <div v-if="schoolInfo.phoneLogin" class="login-action box-justify">
-        <div></div>
-        <a-button type="text" @click="switchLoginType">
-          <i>{{ switchBtnName }}</i>
-          <icon-right />
-        </a-button>
+  <div class="login" @keyup.enter="submit">
+    <div class="login-spin">
+      <h1>欢迎使用</h1>
+      <h4>知学知考-图片导出工具</h4>
+    </div>
+    <div class="login-body">
+      <div class="login-head">
+        <div class="login-head-left">
+          <h2 class="login-head-title">登录</h2>
+          <h4 class="login-head-subtitle">请选择输入</h4>
+        </div>
+        <div class="login-head-right">
+          <a-button class="login-btn btn-setting" @click="toSet">设置</a-button>
+        </div>
       </div>
+      <a-form
+        ref="formRef"
+        :model="formData"
+        :rules="rules"
+        :label-col-props="{ span: 0, offset: 0 }"
+        :wrapper-col-props="{ span: 24, offset: 0 }"
+      >
+        <a-form-item field="schoolCode">
+          <a-select
+            v-model="formData.schoolCode"
+            placeholder="请选择学校"
+            name="schoolCode"
+            :options="schools"
+          >
+          </a-select>
+        </a-form-item>
+        <a-form-item field="loginName">
+          <a-input
+            v-model.trim="formData.loginName"
+            placeholder="请输入账号"
+            name="username"
+            allow-clear
+          >
+          </a-input>
+        </a-form-item>
+        <a-form-item field="password">
+          <a-input
+            v-model.trim="formData.password"
+            type="password"
+            placeholder="请输入密码"
+            allow-clear
+          >
+          </a-input>
+        </a-form-item>
+        <a-form-item>
+          <a-button type="primary" :loading="loading" long @click="submit"
+            >登录</a-button
+          >
+        </a-form-item>
+      </a-form>
     </div>
-
-    <!-- ResetPwd -->
-    <ResetPwd
-      ref="ResetPwdRef"
-      :user-info="userInfo"
-      @modified="resetPwdSuccess"
-      @cancel="resetCancel"
-    />
   </div>
 </template>
 
 <script lang="ts" setup>
-  import { ref, reactive, computed, onMounted } from 'vue';
-  import { Message } from '@arco-design/web-vue';
-  import { useRoute, useRouter } from 'vue-router';
-  import type { FormInstance, FieldRule } from '@arco-design/web-vue/es/form';
-  import { omit } from 'lodash';
+  import { ref, reactive, onMounted } from 'vue';
+  import { useRouter } from 'vue-router';
+  import type { FormInstance } from '@arco-design/web-vue/es/form';
   import useLoading from '@/hooks/loading';
-  import useSms from '@/hooks/sms';
-  import {
-    login,
-    getSchoolInfo,
-    getAccountSmsCode,
-    getSmsCode,
-  } from '@/api/user';
+  import { login, schoolList } from '@/api/user';
   import { getBase64 } from '@/utils/crypto';
-  import { smscode, phone } from '@/utils/formRules';
-  import { getOrgCode } from '@/constants/app';
   import { useAppStore, useUserStore } from '@/store';
-  import { DEFAULT_ROUTE_NAME } from '@/router/constants';
-  import { UserSchoolInfoType, UserState } from '@/store/modules/user/types';
-  import type { LoginData, SchoolInfoRes } from '@/api/types/user';
-  import { ResetPwdUserInfo } from './types';
+  import { UserSchoolInfoType } from '@/store/modules/user/types';
+  import type { LoginData } from '@/api/types/user';
+  import { FormRules, OptionListItem } from '@/types/global';
+  import { objAssign } from '@/utils/utils';
 
-  import ResetPwd from './ResetPwd.vue';
+  defineOptions({
+    name: 'Login',
+  });
 
-  const route = useRoute();
   const router = useRouter();
   const appStore = useAppStore();
   const userStore = useUserStore();
-  appStore.resetInfo();
   userStore.resetInfo();
 
-  const userInfo = ref({} as ResetPwdUserInfo);
-  const schoolInfo = ref({} as SchoolInfoRes);
   const formRef = ref<FormInstance>();
-  const ResetPwdRef = ref();
-  const formData = reactive<Required<LoginData>>({
-    loginName: 'admin-zj',
-    password: 'a12345678',
-    code: '',
-    mobileNumber: '',
-    schoolCode: 'test-school-2',
+  const formData = reactive<LoginData>({
+    schoolCode: '',
     type: 'ACCOUNT',
+    loginName: '',
+    password: '',
   });
-  const rules: Record<string, FieldRule[]> = {
+
+  const rules: FormRules<keyof LoginData> = {
     loginName: [
       {
         required: true,
@@ -172,8 +101,6 @@
         message: '请输入密码',
       },
     ],
-    code: smscode,
-    mobileNumber: phone,
     schoolCode: [
       {
         required: true,
@@ -182,114 +109,23 @@
     ],
   };
 
-  const IS_USERNAME_TYPE = computed(() => {
-    return formData.type === 'ACCOUNT';
-  });
-  const switchBtnName = computed(() => {
-    return formData.type === 'ACCOUNT' ? '短信登录' : '账号登录';
-  });
-
-  async function getSchool() {
-    if (route.params.code) {
-      formData.schoolCode = route.params.code as string;
-      window.sessionStorage.setItem('routeDomainCode', formData.schoolCode);
-    } else {
-      formData.schoolCode = getOrgCode();
-    }
-    const data = await getSchoolInfo(formData.schoolCode);
-    appStore.setInfo({ version: data.version || '' });
-    formData.schoolCode = data.schoolCode;
-    schoolInfo.value = data;
-  }
-
-  function switchLoginType() {
-    if (!schoolInfo.value.phoneLogin) return;
-    formData.type = formData.type === 'ACCOUNT' ? 'PHONE' : 'ACCOUNT';
+  function toSet() {
+    router.push({ name: 'Setting' });
   }
 
-  function resetPwdSuccess(data: UserState) {
-    if (data.schoolInfo && data.schoolInfo.length) {
-      const curSchool = data.schoolInfo.find(
-        (item) => item.code === formData.schoolCode
-      );
-      data.curSchoolInfo = curSchool || ({} as UserSchoolInfoType);
-    }
-    userStore.setInfo(data);
-
-    if (data.roleList && data.roleList.includes('ADMIN')) {
-      router.push({
-        name: 'SelectSchool',
-      });
-    } else {
-      router.push({
-        name: 'Home',
-      });
-    }
-  }
-
-  function resetCancel() {
-    userStore.resetInfo();
-  }
-
-  /* sms  ----start */
-  const { isFetchingCode, codeContent, changeContent } = useSms('login');
-
-  async function fetchMobileSmsCode() {
-    const unvalid = await formRef.value?.validateField([
-      'mobileNumber',
-      'schoolCode',
-    ]);
-    if (unvalid) return;
-
-    isFetchingCode.value = true;
-    const res = await getSmsCode({
-      schoolCode: formData.schoolCode,
-      mobileNumber: formData.mobileNumber,
-    }).catch(() => {
-      isFetchingCode.value = false;
-    });
-    if (!res) return;
-
-    if (res.mobileNumber) {
-      Message.success(
-        `已向手机尾号【${res.mobileNumber.slice(
-          -4
-        )}】成功发送短信,请在2分钟内进行验证!`
-      );
-      changeContent();
-    } else {
-      Message.error('未绑定手机号,请先绑定!');
+  const schools = ref<OptionListItem[]>([]);
+  async function getSchools() {
+    if (!schools.value.length) {
+      schools.value = [{ value: 'test-school-1', label: '学校1' }];
+      return;
     }
-  }
-
-  async function fetchAccountSmsCode() {
-    const unvalid = await formRef.value?.validateField([
-      'loginName',
-      'schoolCode',
-      'password',
-    ]);
-    if (unvalid) return;
 
-    isFetchingCode.value = true;
-    const res = await getAccountSmsCode({
-      schoolCode: formData.schoolCode,
-      loginName: formData.loginName,
-      password: getBase64(formData.password),
-    }).catch(() => {
-      isFetchingCode.value = false;
+    if (!appStore.domain) return;
+    const resData = await schoolList();
+    schools.value = (resData || []).map((item) => {
+      return { value: item.code, label: item.name };
     });
-    if (!res) return;
-
-    if (res) {
-      Message.success(
-        `已向手机尾号【${res.slice(-4)}】成功发送短信,请在2分钟内进行验证!`
-      );
-      changeContent();
-    } else {
-      Message.error('未绑定手机号!');
-    }
   }
-  /* sms  ----end */
 
   /* submit */
   const { loading, setLoading } = useLoading();
@@ -298,18 +134,8 @@
     if (err) return;
 
     setLoading(true);
-    let datas = {} as LoginData;
-    if (IS_USERNAME_TYPE.value) {
-      datas = omit(
-        formData,
-        schoolInfo.value.accountSmsVerify
-          ? ['mobileNumber']
-          : ['code', 'mobileNumber']
-      );
-      datas.password = getBase64(formData.password);
-    } else {
-      datas = omit(formData, ['loginName', 'password']);
-    }
+    const datas = objAssign(formData, {});
+    datas.password = getBase64(formData.password);
     const data = await login(datas).catch(() => {});
     setLoading(false);
     if (!data) return;
@@ -322,39 +148,18 @@
     }
     userStore.setInfo(data);
 
-    // 强制修改密码和绑定手机号
-    if (
-      data.userLoginCheckResult &&
-      (!data.userLoginCheckResult.pwdCount ||
-        (!data.userLoginCheckResult.mobileNumber &&
-          schoolInfo.value.phoneLogin))
-    ) {
-      userInfo.value = {
-        userId: data.userLoginCheckResult.userId,
-        loginName: formData.loginName,
-        schoolCode: formData.schoolCode,
-        password: formData.password,
-        mobileNumber: data.userLoginCheckResult.mobileNumber,
-        pwdCount: data.userLoginCheckResult.pwdCount,
-        phoneLogin: schoolInfo.value.phoneLogin,
-      };
-      ResetPwdRef.value?.open();
-      return;
-    }
+    router.push({
+      name: 'SchoolSelect',
+    });
+  }
 
-    if (data.roleList && data.roleList.includes('ADMIN')) {
-      router.push({
-        name: 'SchoolSelect',
-      });
-    } else {
-      router.push({
-        name: DEFAULT_ROUTE_NAME,
-      });
-    }
+  async function initData() {
+    const domain = await window.db.getDict('domain');
+    appStore.setInfo({ domain });
+    await getSchools();
   }
 
-  // mounted
   onMounted(() => {
-    getSchool();
+    initData();
   });
 </script>

+ 0 - 9
src/views/login/login/types.ts

@@ -1,9 +0,0 @@
-export interface ResetPwdUserInfo {
-  userId: string;
-  loginName: string;
-  schoolCode: string;
-  password: string;
-  mobileNumber: string;
-  pwdCount: number;
-  phoneLogin: boolean;
-}

+ 125 - 0
src/views/login/setting/index.vue

@@ -0,0 +1,125 @@
+<template>
+  <div class="login">
+    <div class="login-spin">
+      <h1>欢迎使用</h1>
+      <h4>知学知考-图片导出工具</h4>
+    </div>
+    <div class="login-body">
+      <div class="login-head">
+        <div class="login-head-left">
+          <h2 class="login-head-title">设置</h2>
+          <h4 class="login-head-subtitle">服务器IP地址设置</h4>
+        </div>
+        <div class="login-head-right"> </div>
+      </div>
+      <a-form
+        ref="formRef"
+        :model="formData"
+        :rules="rules"
+        :label-col-props="{ span: 0, offset: 0 }"
+        :wrapper-col-props="{ span: 24, offset: 0 }"
+      >
+        <a-form-item field="host">
+          <div class="box-justify">
+            <a-select
+              v-model="formData.protocol"
+              style="width: 85px; margin-right: 8px; flex-shrink: 0"
+            >
+              <a-option value="http" label="http"></a-option>
+              <a-option value="https" label="https"></a-option>
+            </a-select>
+            <a-input
+              v-model.trim="formData.host"
+              placeholder="服务器IP地址"
+              clearable
+            ></a-input>
+          </div>
+        </a-form-item>
+        <a-form-item>
+          <a-button
+            type="primary"
+            class="login-submit"
+            style="margin-top: 126px"
+            :disabled="loading"
+            @click="submit"
+            >确定</a-button
+          >
+        </a-form-item>
+      </a-form>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, reactive, onMounted } from 'vue';
+  import { useRouter } from 'vue-router';
+
+  import { Message } from '@arco-design/web-vue';
+  import type { FormInstance } from '@arco-design/web-vue/es/form';
+  import { FormRules } from '@/types/global';
+  import { useAppStore } from '@/store';
+  import useLoading from '@/hooks/loading';
+
+  defineOptions({
+    name: 'Setting',
+  });
+
+  interface FormDataType {
+    protocol: 'http' | 'https';
+    host: string;
+  }
+
+  const appStore = useAppStore();
+  const router = useRouter();
+
+  const formRef = ref<FormInstance>();
+  const formData = reactive<FormDataType>({
+    protocol: 'http',
+    host: '',
+  });
+
+  const rules: FormRules<keyof FormDataType> = {
+    host: [
+      {
+        required: true,
+        message: '请输入服务器IP地址',
+      },
+    ],
+  };
+
+  async function initData() {
+    const domain = await window.db.getDict('domain');
+    if (domain.indexOf('https') !== -1) {
+      formData.protocol = 'https';
+    } else {
+      formData.protocol = 'http';
+    }
+    formData.host = domain.replace(`${formData.protocol}://`, '');
+  }
+
+  // submit
+  const { loading, setLoading } = useLoading();
+  async function submit() {
+    const err = await formRef.value?.validate();
+    if (err) return;
+
+    setLoading(true);
+    const domain = `${formData.protocol}://${formData.host}`;
+    const res = await window.db
+      .updateDict({ key: 'domain', val: domain })
+      .catch(() => {});
+    setLoading(false);
+
+    if (res) {
+      Message.success('设置成功!');
+      appStore.setInfo({ domain });
+      router.go(-1);
+    } else {
+      Message.error('保存失败!');
+    }
+  }
+
+  onMounted(() => {
+    initData();
+  });
+</script>