v3.1.0 1、【新增】9种登录背景图和样式; 2、【新增】全局字体大小切换; 3、【新增】主题颜色切换; 4、【新增】移除cookie保存token,改为使用localStorage; 5、【优化】升级 ant design vue 到最新版本;
							
								
								
									
										1445
									
								
								smart-admin-web/javascript-ant-design-vue3/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						@@ -19,33 +19,32 @@
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@wangeditor/editor": "5.1.14",
 | 
			
		||||
    "@wangeditor/editor-for-vue": "5.1.12",
 | 
			
		||||
    "ant-design-vue": "4.0.7",
 | 
			
		||||
    "axios": "1.4.0",
 | 
			
		||||
    "ant-design-vue": "4.1.2",
 | 
			
		||||
    "axios": "1.6.8",
 | 
			
		||||
    "clipboard": "2.0.11",
 | 
			
		||||
    "crypto-js": "4.1.1",
 | 
			
		||||
    "decimal.js": "10.3.1",
 | 
			
		||||
    "diff": "5.1.0",
 | 
			
		||||
    "diff2html": "3.4.18",
 | 
			
		||||
    "diff": "5.2.0",
 | 
			
		||||
    "diff2html": "3.4.47",
 | 
			
		||||
    "echarts": "5.4.3",
 | 
			
		||||
    "highlight.js": "11.8.0",
 | 
			
		||||
    "js-cookie": "3.0.5",
 | 
			
		||||
    "lodash": "4.17.21",
 | 
			
		||||
    "lunar-javascript": "1.6.3",
 | 
			
		||||
    "mitt": "3.0.0",
 | 
			
		||||
    "lunar-javascript": "1.6.12",
 | 
			
		||||
    "mitt": "3.0.1",
 | 
			
		||||
    "nprogress": "0.2.0",
 | 
			
		||||
    "pinia": "2.1.6",
 | 
			
		||||
    "sm-crypto": "^0.3.13",
 | 
			
		||||
    "pinia": "2.1.7",
 | 
			
		||||
    "sm-crypto": "0.3.13",
 | 
			
		||||
    "sortablejs": "1.15.0",
 | 
			
		||||
    "ua-parser-js": "1.0.35",
 | 
			
		||||
    "v-viewer": "~1.6.4",
 | 
			
		||||
    "vue": "3.3.4",
 | 
			
		||||
    "vue-i18n": "9.2.2",
 | 
			
		||||
    "vue-router": "4.2.4",
 | 
			
		||||
    "vue": "3.3.13",
 | 
			
		||||
    "vue-i18n": "9.10.2",
 | 
			
		||||
    "vue-router": "4.3.0",
 | 
			
		||||
    "vue3-json-viewer": "2.2.2"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@vitejs/plugin-vue": "4.5.0",
 | 
			
		||||
    "@vue/compiler-sfc": "3.3.4",
 | 
			
		||||
    "@vitejs/plugin-vue": "5.0.4",
 | 
			
		||||
    "@vue/compiler-sfc": "3.4.21",
 | 
			
		||||
    "eslint": "^8.16.0",
 | 
			
		||||
    "eslint-config-prettier": "~9.0.0",
 | 
			
		||||
    "eslint-plugin-prettier": "~5.0.0",
 | 
			
		||||
@@ -58,9 +57,9 @@
 | 
			
		||||
    "stylelint-config-prettier": "~9.0.3",
 | 
			
		||||
    "stylelint-config-standard": "~25.0.0",
 | 
			
		||||
    "stylelint-order": "~5.0.0",
 | 
			
		||||
    "terser": "~5.19.2",
 | 
			
		||||
    "vite": "5.0.0",
 | 
			
		||||
    "vue-eslint-parser": "~9.3.1"
 | 
			
		||||
    "terser": "~5.29.2",
 | 
			
		||||
    "vite": "5.2.6",
 | 
			
		||||
    "vue-eslint-parser": "~9.4.2"
 | 
			
		||||
  },
 | 
			
		||||
  "engines": {
 | 
			
		||||
    "node": ">=18"
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,29 @@
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <a-config-provider :locale="antdLocale">
 | 
			
		||||
  <a-config-provider
 | 
			
		||||
    :locale="antdLocale"
 | 
			
		||||
    :theme="{
 | 
			
		||||
      algorithm: compactFlag ? theme.compactAlgorithm : theme.defaultAlgorithm,
 | 
			
		||||
      token: {
 | 
			
		||||
        colorPrimary: themeColors[colorIndex].primaryColor,
 | 
			
		||||
        colorLink: themeColors[colorIndex].primaryColor,
 | 
			
		||||
        colorLinkActive: themeColors[colorIndex].activeColor,
 | 
			
		||||
        colorLinkHover: themeColors[colorIndex].hoverColor,
 | 
			
		||||
        colorIcon: themeColors[colorIndex].primaryColor,
 | 
			
		||||
      },
 | 
			
		||||
      components: {
 | 
			
		||||
        Button: {
 | 
			
		||||
          colorLink: themeColors[colorIndex].primaryColor,
 | 
			
		||||
          colorLinkActive: themeColors[colorIndex].activeColor,
 | 
			
		||||
          colorLinkHover: themeColors[colorIndex].hoverColor,
 | 
			
		||||
        },
 | 
			
		||||
        Icon: {
 | 
			
		||||
          colorIcon: themeColors[colorIndex].primaryColor,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    }"
 | 
			
		||||
  >
 | 
			
		||||
    <!---全局loading--->
 | 
			
		||||
    <a-spin :spinning="spinning" tip="稍等片刻,我在拼命加载中..." size="large">
 | 
			
		||||
      <!--- 路由 -->
 | 
			
		||||
@@ -23,12 +45,21 @@
 | 
			
		||||
  import { computed } from 'vue';
 | 
			
		||||
  import { messages } from '/@/i18n';
 | 
			
		||||
  import { useAppConfigStore } from '/@/store/modules/system/app-config';
 | 
			
		||||
  import { useSpinStore } from './store/modules/system/spin';
 | 
			
		||||
  import { useSpinStore } from '/@/store/modules/system/spin';
 | 
			
		||||
  import { theme } from 'ant-design-vue';
 | 
			
		||||
  import { themeColors } from '/@/theme/color.js';
 | 
			
		||||
 | 
			
		||||
  const antdLocale = computed(() => messages[useAppConfigStore().language].antdLocale);
 | 
			
		||||
  const dayjsLocale = computed(() => messages[useAppConfigStore().language].dayjsLocale);
 | 
			
		||||
  dayjs.locale(dayjsLocale);
 | 
			
		||||
 | 
			
		||||
  // 全局loading
 | 
			
		||||
  let spinStore = useSpinStore();
 | 
			
		||||
  const spinning = computed(() => spinStore.loading);
 | 
			
		||||
  // 是否紧凑
 | 
			
		||||
  const compactFlag = computed(() => useAppConfigStore().compactFlag);
 | 
			
		||||
  // 主题颜色
 | 
			
		||||
  const colorIndex = computed(() => {
 | 
			
		||||
    return useAppConfigStore().colorIndex;
 | 
			
		||||
  });
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 2.4 KiB  | 
| 
		 After Width: | Height: | Size: 3.1 KiB  | 
| 
		 After Width: | Height: | Size: 2.5 KiB  | 
| 
		 Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 3.0 KiB  | 
| 
		 After Width: | Height: | Size: 354 KiB  | 
| 
		 After Width: | Height: | Size: 234 KiB  | 
| 
		 After Width: | Height: | Size: 181 KiB  | 
| 
		 After Width: | Height: | Size: 272 KiB  | 
| 
		 After Width: | Height: | Size: 206 KiB  | 
| 
		 Before Width: | Height: | Size: 394 KiB After Width: | Height: | Size: 394 KiB  | 
| 
		 Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 3.6 KiB  | 
| 
		 Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 3.6 KiB  | 
| 
		 Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 3.3 KiB  | 
@@ -16,6 +16,8 @@ export const appDefaultConfig = {
 | 
			
		||||
  sideMenuWidth: 200,
 | 
			
		||||
  // 菜单主题
 | 
			
		||||
  sideMenuTheme: 'dark',
 | 
			
		||||
  // 主题颜色索引
 | 
			
		||||
  colorIndex: 0,
 | 
			
		||||
  // 顶部菜单页面宽度
 | 
			
		||||
  pageWidth: '99%',
 | 
			
		||||
  // 标签页
 | 
			
		||||
@@ -32,4 +34,6 @@ export const appDefaultConfig = {
 | 
			
		||||
  websiteName: 'SmartAdmin 3.X',
 | 
			
		||||
  // 主题颜色
 | 
			
		||||
  primaryColor: 'red',
 | 
			
		||||
  // 紧凑
 | 
			
		||||
  compactFlag: false,
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,8 @@ const KEY_PREFIX = 'smart_admin_';
 | 
			
		||||
 * localStorageKey集合
 | 
			
		||||
 */
 | 
			
		||||
export default {
 | 
			
		||||
  // 用户token
 | 
			
		||||
  USER_TOKEN: `${KEY_PREFIX}user_token`,
 | 
			
		||||
  // 用户信息
 | 
			
		||||
  USER_INFO: `${KEY_PREFIX}user_info`,
 | 
			
		||||
  // 用户权限点
 | 
			
		||||
 
 | 
			
		||||
@@ -14,10 +14,12 @@ export default {
 | 
			
		||||
  antdLocale: antd,
 | 
			
		||||
  dayjsLocale: dayjs,
 | 
			
		||||
  'setting.title': 'Setting',
 | 
			
		||||
  'setting.color': 'Theme Color',
 | 
			
		||||
  'setting.menu.layout': 'Menu Layout',
 | 
			
		||||
  'setting.menu.width': 'Menu Width',
 | 
			
		||||
  'setting.menu.theme': 'Menu Theme',
 | 
			
		||||
  'setting.page.width': 'Page Width',
 | 
			
		||||
  'setting.compact': 'Page Compact',
 | 
			
		||||
  'setting.bread': 'Show Bread',
 | 
			
		||||
  'setting.pagetag': 'Show PageTag',
 | 
			
		||||
  'setting.footer': 'Show Footer',
 | 
			
		||||
 
 | 
			
		||||
@@ -14,9 +14,11 @@ export default {
 | 
			
		||||
  antdLocale: antd,
 | 
			
		||||
  dayjsLocale: dayjs,
 | 
			
		||||
  'setting.title': '网站设置',
 | 
			
		||||
  'setting.color': '主题颜色',
 | 
			
		||||
  'setting.menu.layout': '菜单布局',
 | 
			
		||||
  'setting.menu.width': '菜单宽度',
 | 
			
		||||
  'setting.menu.theme': '菜单主题',
 | 
			
		||||
  'setting.compact': '页面紧凑',
 | 
			
		||||
  'setting.page.width': '页面宽度',
 | 
			
		||||
  'setting.bread': '面包屑',
 | 
			
		||||
  'setting.pagetag': '标签页',
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,6 @@
 | 
			
		||||
  import { computed, ref, onMounted } from 'vue';
 | 
			
		||||
  import { loginApi } from '/src/api/system/login-api';
 | 
			
		||||
  import { useUserStore } from '/@/store/modules/system/user';
 | 
			
		||||
  import { clearAllCoolies } from '/@/utils/cookie-util';
 | 
			
		||||
  import { localClear } from '/@/utils/local-util';
 | 
			
		||||
  import { smartSentry } from '/@/lib/smart-sentry';
 | 
			
		||||
  import HeaderResetPassword from './header-reset-password-modal/index.vue';
 | 
			
		||||
@@ -48,9 +47,7 @@
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      smartSentry.captureError(e);
 | 
			
		||||
    } finally {
 | 
			
		||||
 | 
			
		||||
      localClear();
 | 
			
		||||
      clearAllCoolies();
 | 
			
		||||
      useUserStore().logout();
 | 
			
		||||
      location.reload();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -11,10 +11,35 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <a-drawer :title="$t('setting.title')" placement="right" :open="visible" @close="close">
 | 
			
		||||
    <a-form layout="horizontal" :label-col="{ span: 8 }">
 | 
			
		||||
      <a-form-item label="语言/Language">
 | 
			
		||||
        <a-select v-model:value="formState.language" @change="changeLanguage" style="width: 120px">
 | 
			
		||||
          <a-select-option v-for="item in i18nList" :key="item.value" :value="item.value">{{ item.text }}</a-select-option>
 | 
			
		||||
        </a-select>
 | 
			
		||||
      <a-form-item :label="$t('setting.color')">
 | 
			
		||||
        <div style="display: flex; align-items: center">
 | 
			
		||||
          <template v-for="(item, index) in themeColors">
 | 
			
		||||
            <div v-if="index === formState.colorIndex" class="color">
 | 
			
		||||
              <CheckSquareFilled :style="{ color: item.primaryColor, fontSize: '22px' }" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div v-else @click="changeColor(index)" class="color">
 | 
			
		||||
              <svg
 | 
			
		||||
                class="icon"
 | 
			
		||||
                viewBox="0 0 1024 1024"
 | 
			
		||||
                version="1.1"
 | 
			
		||||
                :fill="item.primaryColor"
 | 
			
		||||
                xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
                width="26"
 | 
			
		||||
                height="26"
 | 
			
		||||
              >
 | 
			
		||||
                <path
 | 
			
		||||
                  d="M128 160.01219c0-17.67619 14.336-32.01219 32.01219-32.01219h704c17.65181 0 31.98781 14.336 31.98781 32.01219v704c0 17.65181-14.336 31.98781-32.01219 31.98781H160.036571a31.98781 31.98781 0 0 1-32.01219-32.01219V160.036571z"
 | 
			
		||||
                ></path>
 | 
			
		||||
              </svg>
 | 
			
		||||
            </div>
 | 
			
		||||
          </template>
 | 
			
		||||
        </div>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item :label="$t('setting.compact')">
 | 
			
		||||
        <a-radio-group v-model:value="formState.compactFlag" button-style="solid" @change="changeCompactFlag">
 | 
			
		||||
          <a-radio-button :value="false">默认</a-radio-button>
 | 
			
		||||
          <a-radio-button :value="true">紧凑</a-radio-button>
 | 
			
		||||
        </a-radio-group>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item :label="$t('setting.menu.layout')">
 | 
			
		||||
        <a-radio-group @change="changeLayout" button-style="solid" v-model:value="formState.layout">
 | 
			
		||||
@@ -23,6 +48,12 @@
 | 
			
		||||
          </a-radio-button>
 | 
			
		||||
        </a-radio-group>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item :label="$t('setting.menu.theme')">
 | 
			
		||||
        <a-radio-group v-model:value="formState.sideMenuTheme" button-style="solid" @change="changeMenuTheme">
 | 
			
		||||
          <a-radio-button value="dark">Dark</a-radio-button>
 | 
			
		||||
          <a-radio-button value="light">Light</a-radio-button>
 | 
			
		||||
        </a-radio-group>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item :label="$t('setting.menu.width')" v-if="formState.layout === LAYOUT_ENUM.SIDE.value">
 | 
			
		||||
        <a-input-number @change="changeSideMenuWidth" v-model:value="formState.sideMenuWidth" :min="1" />
 | 
			
		||||
        像素(px)
 | 
			
		||||
@@ -31,11 +62,10 @@
 | 
			
		||||
        <a-input @change="changePageWidth" v-model:value="formState.pageWidth" />
 | 
			
		||||
        像素(px)或者 百分比
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item :label="$t('setting.menu.theme')">
 | 
			
		||||
        <a-radio-group v-model:value="formState.sideMenuTheme" button-style="solid" @change="changeMenuTheme">
 | 
			
		||||
          <a-radio-button value="dark">Dark</a-radio-button>
 | 
			
		||||
          <a-radio-button value="light">Light</a-radio-button>
 | 
			
		||||
        </a-radio-group>
 | 
			
		||||
      <a-form-item label="语言/Language">
 | 
			
		||||
        <a-select v-model:value="formState.language" @change="changeLanguage" style="width: 120px">
 | 
			
		||||
          <a-select-option v-for="item in i18nList" :key="item.value" :value="item.value">{{ item.text }}</a-select-option>
 | 
			
		||||
        </a-select>
 | 
			
		||||
      </a-form-item>
 | 
			
		||||
      <a-form-item :label="$t('setting.bread')">
 | 
			
		||||
        <a-switch @change="changeBreadCrumbFlag" v-model:checked="formState.breadCrumbFlag" checked-children="显示" un-checked-children="隐藏" />
 | 
			
		||||
@@ -69,6 +99,7 @@
 | 
			
		||||
  import { useAppConfigStore } from '/@/store/modules/system/app-config';
 | 
			
		||||
  import { Modal } from 'ant-design-vue';
 | 
			
		||||
  import { appDefaultConfig } from '/@/config/app-config';
 | 
			
		||||
  import { themeColors } from '/@/theme/color.js';
 | 
			
		||||
 | 
			
		||||
  // ----------------- modal 显示与隐藏 -----------------
 | 
			
		||||
 | 
			
		||||
@@ -130,10 +161,14 @@
 | 
			
		||||
    layout: appConfigStore.layout,
 | 
			
		||||
    // 页面宽度
 | 
			
		||||
    pageWidth: appConfigStore.pageWidth,
 | 
			
		||||
    // 颜色
 | 
			
		||||
    colorIndex: appConfigStore.colorIndex,
 | 
			
		||||
    // 侧边菜单宽度
 | 
			
		||||
    sideMenuWidth: appConfigStore.sideMenuWidth,
 | 
			
		||||
    // 菜单主题
 | 
			
		||||
    sideMenuTheme: appConfigStore.sideMenuTheme,
 | 
			
		||||
    // 页面紧凑
 | 
			
		||||
    compactFlag: appConfigStore.compactFlag,
 | 
			
		||||
    // 标签页
 | 
			
		||||
    pageTagFlag: appConfigStore.pageTagFlag,
 | 
			
		||||
    // 面包屑
 | 
			
		||||
@@ -162,6 +197,13 @@
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function changeColor(index) {
 | 
			
		||||
    formState.colorIndex = index;
 | 
			
		||||
    appConfigStore.$patch({
 | 
			
		||||
      colorIndex: index,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function changeSideMenuWidth(value) {
 | 
			
		||||
    appConfigStore.$patch({
 | 
			
		||||
      sideMenuWidth: value,
 | 
			
		||||
@@ -180,6 +222,12 @@
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function changeCompactFlag(e) {
 | 
			
		||||
    appConfigStore.$patch({
 | 
			
		||||
      compactFlag: e.target.value,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function changeBreadCrumbFlag(e) {
 | 
			
		||||
    appConfigStore.$patch({
 | 
			
		||||
      breadCrumbFlag: e,
 | 
			
		||||
@@ -222,4 +270,14 @@
 | 
			
		||||
    text-align: left;
 | 
			
		||||
    z-index: 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .color {
 | 
			
		||||
    margin-left: 8px;
 | 
			
		||||
    display: inline;
 | 
			
		||||
    height: 26px;
 | 
			
		||||
    width: 26px;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -34,9 +34,9 @@
 | 
			
		||||
      <HeaderAvatar />
 | 
			
		||||
    </div>
 | 
			
		||||
    <!---帮助文档--->
 | 
			
		||||
    <div class="user-space-item" @click="showHelpDoc">
 | 
			
		||||
      <question-circle-two-tone style="font-size: 18px; margin-right: 5px; margin-top: 5px" />
 | 
			
		||||
    <div class="user-space-item" @click="showHelpDoc" v-if="!showHelpDocFlag">
 | 
			
		||||
      <span>帮助文档</span>
 | 
			
		||||
      <DoubleLeftOutlined v-if="!showHelpDocFlag" />
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <HeaderSetting ref="headerSetting" />
 | 
			
		||||
@@ -48,7 +48,8 @@
 | 
			
		||||
  import HeaderSetting from './header-setting.vue';
 | 
			
		||||
  import HeaderMessage from './header-message.vue';
 | 
			
		||||
  import { useAppConfigStore } from '/@/store/modules/system/app-config';
 | 
			
		||||
  import { ref } from 'vue';
 | 
			
		||||
  import { computed, ref } from 'vue';
 | 
			
		||||
  import { theme } from 'ant-design-vue';
 | 
			
		||||
 | 
			
		||||
  // 设置
 | 
			
		||||
  const headerSetting = ref();
 | 
			
		||||
@@ -67,10 +68,17 @@
 | 
			
		||||
    useAppConfigStore().showHelpDoc();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const showHelpDocFlag = computed(() => {
 | 
			
		||||
    return useAppConfigStore().helpDocFlag;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  //搜索
 | 
			
		||||
  function search(){
 | 
			
		||||
    window.open("https://1024lab.net");
 | 
			
		||||
  function search() {
 | 
			
		||||
    window.open('https://1024lab.net');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const { useToken } = theme;
 | 
			
		||||
  const { token } = useToken();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="less" scoped>
 | 
			
		||||
@@ -91,7 +99,7 @@
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .user-space-item:hover {
 | 
			
		||||
    color: @primary-color;
 | 
			
		||||
    color: v-bind('token.colorPrimary');
 | 
			
		||||
    background: @hover-bg-color;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@
 | 
			
		||||
  <a-row style="border-bottom: 1px solid #eeeeee; position: relative" v-show="pageTagFlag">
 | 
			
		||||
    <a-dropdown :trigger="['contextmenu']">
 | 
			
		||||
      <div class="smart-page-tag">
 | 
			
		||||
        <a-tabs style="width: 100%" :tab-position="mode" v-model:activeKey="selectedKey" size="small" @tabClick="selectTab" >
 | 
			
		||||
        <a-tabs style="width: 100%" :tab-position="mode" v-model:activeKey="selectedKey" size="small" @tabClick="selectTab">
 | 
			
		||||
          <a-tab-pane v-for="item in tagNav" :key="item.menuName">
 | 
			
		||||
            <template #tab>
 | 
			
		||||
              <span>
 | 
			
		||||
@@ -55,12 +55,7 @@
 | 
			
		||||
  import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
 | 
			
		||||
  import { useAppConfigStore } from '/@/store/modules/system/app-config';
 | 
			
		||||
  import { useUserStore } from '/@/store/modules/system/user';
 | 
			
		||||
 | 
			
		||||
  // //样式
 | 
			
		||||
  // const tagOperateWidth = ref(40);
 | 
			
		||||
  // const tabBarStyle = {
 | 
			
		||||
  //   width: 'calc(100% - 80px)'
 | 
			
		||||
  // }
 | 
			
		||||
  import { theme } from 'ant-design-vue';
 | 
			
		||||
 | 
			
		||||
  //标签页 是否显示
 | 
			
		||||
  const pageTagFlag = computed(() => useAppConfigStore().$state.pageTagFlag);
 | 
			
		||||
@@ -85,7 +80,7 @@
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    // 寻找tag
 | 
			
		||||
    let tag = tagNav.value.find((e) => e.menuName == name);
 | 
			
		||||
    let tag = tagNav.value.find((e) => e.menuName === name);
 | 
			
		||||
    if (!tag) {
 | 
			
		||||
      router.push({ name: HOME_PAGE_NAME });
 | 
			
		||||
      return;
 | 
			
		||||
@@ -96,7 +91,7 @@
 | 
			
		||||
 | 
			
		||||
  //通过菜单关闭
 | 
			
		||||
  function closeByMenu(closeAll) {
 | 
			
		||||
    let find = tagNav.value.find((e) => e.menuName == selectedKey.value);
 | 
			
		||||
    let find = tagNav.value.find((e) => e.menuName === selectedKey.value);
 | 
			
		||||
    if (!find || closeAll) {
 | 
			
		||||
      closeTag(null, true);
 | 
			
		||||
    } else {
 | 
			
		||||
@@ -110,12 +105,12 @@
 | 
			
		||||
    if (item && !closeAll) {
 | 
			
		||||
      let goName = HOME_PAGE_NAME;
 | 
			
		||||
      let goQuery = undefined;
 | 
			
		||||
      if (item.fromMenuName && tagNav.value.some((e) => e.menuName == item.fromMenuName)) {
 | 
			
		||||
      if (item.fromMenuName && tagNav.value.some((e) => e.menuName === item.fromMenuName)) {
 | 
			
		||||
        goName = item.fromMenuName;
 | 
			
		||||
        goQuery = item.fromMenuQuery;
 | 
			
		||||
      } else {
 | 
			
		||||
        // 查询左侧tag
 | 
			
		||||
        let index = tagNav.value.findIndex((e) => e.menuName == item.menuName);
 | 
			
		||||
        let index = tagNav.value.findIndex((e) => e.menuName === item.menuName);
 | 
			
		||||
        if (index > 0) {
 | 
			
		||||
          // 查询左侧tag
 | 
			
		||||
          let leftTagNav = tagNav.value[index - 1];
 | 
			
		||||
@@ -132,10 +127,14 @@
 | 
			
		||||
    // 关闭其他tag不做处理 直接调用closeTagNav
 | 
			
		||||
    useUserStore().closeTagNav(item ? item.menuName : null, closeAll);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const { useToken } = theme;
 | 
			
		||||
  const { token } = useToken();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="less">
 | 
			
		||||
  @smart-page-tag-operate-width: 40px;
 | 
			
		||||
  @color-primary: v-bind('token.colorPrimary');
 | 
			
		||||
 | 
			
		||||
  .smart-page-tag-operate {
 | 
			
		||||
    width: @smart-page-tag-operate-width;
 | 
			
		||||
@@ -164,7 +163,7 @@
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .smart-page-tag-operate:hover {
 | 
			
		||||
    color: @primary-color;
 | 
			
		||||
    color: @color-primary;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .smart-page-tag {
 | 
			
		||||
@@ -184,7 +183,7 @@
 | 
			
		||||
    .smart-page-tag-close {
 | 
			
		||||
      margin-left: 5px;
 | 
			
		||||
      font-size: 10px;
 | 
			
		||||
      color: #8c8c8c;
 | 
			
		||||
      color: #666666;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**  覆盖 ant design vue的 tabs 样式,变小一点 **/
 | 
			
		||||
@@ -203,15 +202,15 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    :deep(.ant-tabs-tab-active) {
 | 
			
		||||
      background-color: #e8f4ff;
 | 
			
		||||
      background-color: #eeeeee;
 | 
			
		||||
      .smart-page-tag-close {
 | 
			
		||||
        color: @primary-color;
 | 
			
		||||
        color: @color-primary;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    :deep(.ant-tabs-nav .ant-tabs-tab:hover) {
 | 
			
		||||
      background-color: #e8f4ff;
 | 
			
		||||
      background-color: #eeeeee;
 | 
			
		||||
      .smart-page-tag-close {
 | 
			
		||||
        color: @primary-color;
 | 
			
		||||
        color: @color-primary;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -90,17 +90,20 @@
 | 
			
		||||
    z-index: 100;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
 | 
			
		||||
    .logo-img {
 | 
			
		||||
      width: 40px;
 | 
			
		||||
      height: 40px;
 | 
			
		||||
      width: 30px;
 | 
			
		||||
      height: 30px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .title {
 | 
			
		||||
      font-size: 16px;
 | 
			
		||||
      font-weight: 600;
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
      word-wrap: break-word;
 | 
			
		||||
      white-space: nowrap;
 | 
			
		||||
      color: v-bind('theme === "light" ? "#001529": "#ffffff"');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -79,9 +79,10 @@
 | 
			
		||||
      z-index: 21;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      justify-content: center;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      .logo-img {
 | 
			
		||||
        width: 32px;
 | 
			
		||||
        height: 32px;
 | 
			
		||||
        width: 30px;
 | 
			
		||||
        height: 30px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -93,10 +94,11 @@
 | 
			
		||||
      display: flex;
 | 
			
		||||
      cursor: pointer;
 | 
			
		||||
      justify-content: center;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
 | 
			
		||||
      .logo-img {
 | 
			
		||||
        width: 40px;
 | 
			
		||||
        height: 40px;
 | 
			
		||||
        width: 30px;
 | 
			
		||||
        height: 30px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .title {
 | 
			
		||||
 
 | 
			
		||||
@@ -86,10 +86,10 @@
 | 
			
		||||
 | 
			
		||||
  const color = computed(() => {
 | 
			
		||||
    let isLight = useAppConfigStore().$state.sideMenuTheme === 'light';
 | 
			
		||||
      return {
 | 
			
		||||
          color: isLight ? '#001529' : '#FFFFFF',
 | 
			
		||||
          background:isLight ? '#FFFFFF' : '#001529',
 | 
			
		||||
      }
 | 
			
		||||
    return {
 | 
			
		||||
      color: isLight ? '#001529' : '#FFFFFF',
 | 
			
		||||
      background: isLight ? '#FFFFFF' : '#001529',
 | 
			
		||||
    };
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const router = useRouter();
 | 
			
		||||
@@ -118,7 +118,7 @@
 | 
			
		||||
 | 
			
		||||
      .logo-img {
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
        height: 45px;
 | 
			
		||||
        height: 30px;
 | 
			
		||||
        vertical-align: middle;
 | 
			
		||||
      }
 | 
			
		||||
      .title {
 | 
			
		||||
@@ -161,13 +161,13 @@
 | 
			
		||||
        color: v-bind('color.color');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .user-space-item{
 | 
			
		||||
      .user-space-item {
 | 
			
		||||
        margin-left: 10px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  :deep(.ant-menu-horizontal){
 | 
			
		||||
    border-bottom:0;
 | 
			
		||||
  :deep(.ant-menu-horizontal) {
 | 
			
		||||
    border-bottom: 0;
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,11 +9,11 @@
 | 
			
		||||
 */
 | 
			
		||||
import { message, Modal } from 'ant-design-vue';
 | 
			
		||||
import axios from 'axios';
 | 
			
		||||
import { clearAllCoolies, getTokenFromCookie } from '/@/utils/cookie-util';
 | 
			
		||||
import { localClear } from '/@/utils/local-util';
 | 
			
		||||
import { localClear, localRead } from '/@/utils/local-util';
 | 
			
		||||
import { decryptData, encryptData } from './encrypt';
 | 
			
		||||
import { DATA_TYPE_ENUM } from '../constants/common-const';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
 | 
			
		||||
 | 
			
		||||
// token的消息头
 | 
			
		||||
const TOKEN_HEADER = 'x-access-token';
 | 
			
		||||
@@ -28,7 +28,7 @@ const smartAxios = axios.create({
 | 
			
		||||
smartAxios.interceptors.request.use(
 | 
			
		||||
  (config) => {
 | 
			
		||||
    // 在发送请求之前消息头加入token token
 | 
			
		||||
    const token = getTokenFromCookie();
 | 
			
		||||
    const token = localRead(LocalStorageKeyConst.USER_TOKEN);
 | 
			
		||||
    if (token) {
 | 
			
		||||
      config.headers[TOKEN_HEADER] = token;
 | 
			
		||||
    } else {
 | 
			
		||||
@@ -73,7 +73,6 @@ smartAxios.interceptors.response.use(
 | 
			
		||||
      if (res.code === 30007 || res.code === 30008) {
 | 
			
		||||
        message.destroy();
 | 
			
		||||
        message.error('您没有登录,请重新登录');
 | 
			
		||||
        clearAllCoolies();
 | 
			
		||||
        localClear();
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
          location.href = '/';
 | 
			
		||||
@@ -97,7 +96,6 @@ smartAxios.interceptors.response.use(
 | 
			
		||||
          content: res.msg,
 | 
			
		||||
          onOk() {
 | 
			
		||||
            return new Promise((resolve, reject) => {
 | 
			
		||||
              clearAllCoolies();
 | 
			
		||||
              localClear();
 | 
			
		||||
              setTimeout(() => {
 | 
			
		||||
                location.href = '/';
 | 
			
		||||
 
 | 
			
		||||
@@ -25,8 +25,10 @@ import smartEnumPlugin from '/@/plugins/smart-enums-plugin';
 | 
			
		||||
import { buildRoutes, router } from '/@/router';
 | 
			
		||||
import { store } from '/@/store';
 | 
			
		||||
import { useUserStore } from '/@/store/modules/system/user';
 | 
			
		||||
import 'ant-design-vue/dist/reset.css';
 | 
			
		||||
import '/@/theme/index.less';
 | 
			
		||||
import { getTokenFromCookie } from '/@/utils/cookie-util';
 | 
			
		||||
import { localRead } from '/@/utils/local-util.js';
 | 
			
		||||
import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * -------------------- ※ 着重 解释说明下main.js的初始化逻辑 begin ※ --------------------
 | 
			
		||||
@@ -82,7 +84,7 @@ function initVue() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//不需要获取用户信息、用户菜单、用户菜单动态路由,直接初始化vue即可
 | 
			
		||||
let token = getTokenFromCookie();
 | 
			
		||||
let token = localRead(LocalStorageKeyConst.USER_TOKEN);
 | 
			
		||||
if (!token) {
 | 
			
		||||
  initVue();
 | 
			
		||||
} else {
 | 
			
		||||
 
 | 
			
		||||
@@ -16,9 +16,9 @@ import { PAGE_PATH_404, PAGE_PATH_LOGIN } from '/@/constants/common-const';
 | 
			
		||||
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
 | 
			
		||||
import SmartLayout from '../layout/index.vue';
 | 
			
		||||
import { useUserStore } from '/@/store/modules/system/user';
 | 
			
		||||
import { clearAllCoolies, getTokenFromCookie } from '/@/utils/cookie-util';
 | 
			
		||||
import { localClear } from '/@/utils/local-util';
 | 
			
		||||
import { localClear, localRead } from '/@/utils/local-util';
 | 
			
		||||
import _ from 'lodash';
 | 
			
		||||
import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
 | 
			
		||||
 | 
			
		||||
export const router = createRouter({
 | 
			
		||||
  history: createWebHashHistory(),
 | 
			
		||||
@@ -39,9 +39,8 @@ router.beforeEach(async (to, from, next) => {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 验证登录
 | 
			
		||||
  const token = getTokenFromCookie();
 | 
			
		||||
  const token = localRead(LocalStorageKeyConst.USER_TOKEN);
 | 
			
		||||
  if (!token) {
 | 
			
		||||
    clearAllCoolies();
 | 
			
		||||
    localClear();
 | 
			
		||||
    next({ path: PAGE_PATH_LOGIN });
 | 
			
		||||
    return;
 | 
			
		||||
 
 | 
			
		||||
@@ -17,5 +17,5 @@ export const loginRouters = [
 | 
			
		||||
      title: '登录',
 | 
			
		||||
      hideInMenu: true,
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,8 @@ import { defineStore } from 'pinia';
 | 
			
		||||
import localKey from '/@/constants/local-storage-key-const';
 | 
			
		||||
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
 | 
			
		||||
import { MENU_TYPE_ENUM } from '/@/constants/system/menu-const';
 | 
			
		||||
import { getTokenFromCookie } from '/@/utils/cookie-util';
 | 
			
		||||
import { localClear, localRead, localSave } from '/@/utils/local-util';
 | 
			
		||||
import LocalStorageKeyConst from '/@/constants/local-storage-key-const';
 | 
			
		||||
 | 
			
		||||
export const useUserStore = defineStore({
 | 
			
		||||
  id: 'userStore',
 | 
			
		||||
@@ -61,7 +61,7 @@ export const useUserStore = defineStore({
 | 
			
		||||
      if (state.token) {
 | 
			
		||||
        return state.token;
 | 
			
		||||
      }
 | 
			
		||||
      return getTokenFromCookie();
 | 
			
		||||
      return localRead(LocalStorageKeyConst.USER_TOKEN);
 | 
			
		||||
    },
 | 
			
		||||
    //是否初始化了 路由
 | 
			
		||||
    getMenuRouterInitFlag(state) {
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,38 @@
 | 
			
		||||
export const themeColors = [
 | 
			
		||||
  // 蓝色
 | 
			
		||||
  {
 | 
			
		||||
    primaryColor: '#1677ff',
 | 
			
		||||
    activeColor: '#0958d9',
 | 
			
		||||
    hoverColor: '#bae0ff',
 | 
			
		||||
  },
 | 
			
		||||
  // 紫色
 | 
			
		||||
  {
 | 
			
		||||
    primaryColor: '#722ED1',
 | 
			
		||||
    activeColor: '#531dab',
 | 
			
		||||
    hoverColor: '#9254de',
 | 
			
		||||
  },
 | 
			
		||||
  // 红色
 | 
			
		||||
  {
 | 
			
		||||
    primaryColor: '#F5222D',
 | 
			
		||||
    activeColor: '#cf1322',
 | 
			
		||||
    hoverColor: '#ff4d4f',
 | 
			
		||||
  },
 | 
			
		||||
  // 青色
 | 
			
		||||
  {
 | 
			
		||||
    primaryColor: '#13c2c2',
 | 
			
		||||
    activeColor: '#08979c',
 | 
			
		||||
    hoverColor: '#36cfc9',
 | 
			
		||||
  },
 | 
			
		||||
  // 粉色
 | 
			
		||||
  {
 | 
			
		||||
    primaryColor: '#EB2F96',
 | 
			
		||||
    activeColor: '#c41d7f',
 | 
			
		||||
    hoverColor: '#f759ab',
 | 
			
		||||
  },
 | 
			
		||||
  // 绿色
 | 
			
		||||
  {
 | 
			
		||||
    primaryColor: '#52C41A',
 | 
			
		||||
    activeColor: '#389e0d',
 | 
			
		||||
    hoverColor: '#73d13d',
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
@@ -1,29 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * cookie相关操作
 | 
			
		||||
 *
 | 
			
		||||
 * @Author:    1024创新实验室-主任:卓大
 | 
			
		||||
 * @Date:      2022-09-06 20:58:49
 | 
			
		||||
 * @Wechat:    zhuda1024
 | 
			
		||||
 * @Email:     lab1024@163.com
 | 
			
		||||
 * @Copyright  1024创新实验室 ( https://1024lab.net ),Since 2012
 | 
			
		||||
 */
 | 
			
		||||
import Cookies from 'js-cookie';
 | 
			
		||||
 | 
			
		||||
export const COOKIE_TOKEN_KEY = 'user_token';
 | 
			
		||||
 | 
			
		||||
export const clearAllCoolies = () => {
 | 
			
		||||
  Cookies.remove(COOKIE_TOKEN_KEY);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getTokenFromCookie = () => {
 | 
			
		||||
  return Cookies.get(COOKIE_TOKEN_KEY);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 一年后cookie过期
 | 
			
		||||
 *
 | 
			
		||||
 * @param token
 | 
			
		||||
 */
 | 
			
		||||
export const saveTokenToCookie = (token) => {
 | 
			
		||||
  Cookies.set(COOKIE_TOKEN_KEY, token, { expires: 365 });
 | 
			
		||||
};
 | 
			
		||||
@@ -7,6 +7,7 @@
 | 
			
		||||
 * @Email:     lab1024@163.com
 | 
			
		||||
 * @Copyright  1024创新实验室 ( https://1024lab.net ),Since 2012
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
export const localSave = (key, value) => {
 | 
			
		||||
  localStorage.setItem(key, value);
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
  *
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <default-home-card extra="更多" icon="FireTwoTone" title="更新日志" @extraClick="onMore">
 | 
			
		||||
  <default-home-card extra="更多" icon="FlagOutlined" title="更新日志" @extraClick="onMore">
 | 
			
		||||
    <a-empty v-if="$lodash.isEmpty(data)" />
 | 
			
		||||
    <ul v-else>
 | 
			
		||||
      <template v-for="(item, index) in data" :key="index">
 | 
			
		||||
 
 | 
			
		||||
@@ -13,9 +13,9 @@
 | 
			
		||||
    <a-card size="small">
 | 
			
		||||
      <template #title>
 | 
			
		||||
        <div class="title">
 | 
			
		||||
          <component :is="$antIcons[props.icon]" v-if="props.icon" :style="{ fontSize: '18px' }" />
 | 
			
		||||
          <component :is="$antIcons[props.icon]" v-if="props.icon" :style="{ fontSize: '18px', color: token.colorPrimary }" />
 | 
			
		||||
          <slot name="title"></slot>
 | 
			
		||||
          <span v-if="!$slots.title" class="smart-margin-left10">{{ props.title }}</span>
 | 
			
		||||
          <span v-if="!$slots.title" class="smart-margin-left10">{{ props.title }} </span>
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
      <template v-if="props.extra" #extra>
 | 
			
		||||
@@ -27,34 +27,43 @@
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup>
 | 
			
		||||
let props = defineProps({
 | 
			
		||||
  icon: String,
 | 
			
		||||
  title: String,
 | 
			
		||||
  extra: String,
 | 
			
		||||
});
 | 
			
		||||
let emits = defineEmits(['extraClick']);
 | 
			
		||||
  import { theme } from 'ant-design-vue';
 | 
			
		||||
  import { computed } from 'vue';
 | 
			
		||||
 | 
			
		||||
function extraClick() {
 | 
			
		||||
  emits('extraClick');
 | 
			
		||||
}
 | 
			
		||||
  let props = defineProps({
 | 
			
		||||
    icon: String,
 | 
			
		||||
    title: String,
 | 
			
		||||
    extra: String,
 | 
			
		||||
  });
 | 
			
		||||
  let emits = defineEmits(['extraClick']);
 | 
			
		||||
 | 
			
		||||
  function extraClick() {
 | 
			
		||||
    emits('extraClick');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const { useToken } = theme;
 | 
			
		||||
  const { token } = useToken();
 | 
			
		||||
  const color = computed(() => {
 | 
			
		||||
    return token.colorPrimary;
 | 
			
		||||
  });
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="less" scoped>
 | 
			
		||||
.card-container {
 | 
			
		||||
  background-color: #fff;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  .card-container {
 | 
			
		||||
    background-color: #fff;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
 | 
			
		||||
  .title {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    &::before {
 | 
			
		||||
      content: '';
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      top: 3px;
 | 
			
		||||
      left: 0;
 | 
			
		||||
      width: 3px;
 | 
			
		||||
      height: 30px;
 | 
			
		||||
      background-color: @primary-color;
 | 
			
		||||
    .title {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      &::before {
 | 
			
		||||
        content: '';
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        top: 3px;
 | 
			
		||||
        left: 0;
 | 
			
		||||
        width: 3px;
 | 
			
		||||
        height: 30px;
 | 
			
		||||
        background-color: v-bind('token.colorPrimary');
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <default-home-card icon="ProfileTwoTone" title="销量统计">
 | 
			
		||||
  <default-home-card icon="Profile" title="销量统计">
 | 
			
		||||
    <div class="echarts-box">
 | 
			
		||||
      <div class="category-main" id="category-main"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,113 +7,112 @@
 | 
			
		||||
 * @FilePath: /smart-admin/src/views/system/home/components/gauge.vue
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <default-home-card icon="RocketTwoTone" title="业绩完成度">
 | 
			
		||||
  <default-home-card icon="Rocket" title="业绩完成度">
 | 
			
		||||
    <div class="echarts-box">
 | 
			
		||||
      <div id="gauge-main" class="gauge-main"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </default-home-card>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup>
 | 
			
		||||
import DefaultHomeCard from "/@/views/system/home/components/default-home-card.vue";
 | 
			
		||||
import * as echarts from "echarts";
 | 
			
		||||
import {onMounted, watch} from "vue";
 | 
			
		||||
import {reactive} from "vue";
 | 
			
		||||
  import DefaultHomeCard from '/@/views/system/home/components/default-home-card.vue';
 | 
			
		||||
  import * as echarts from 'echarts';
 | 
			
		||||
  import { onMounted, watch } from 'vue';
 | 
			
		||||
  import { reactive } from 'vue';
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  percent: {
 | 
			
		||||
    type: Number,
 | 
			
		||||
    default: 0
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
  const props = defineProps({
 | 
			
		||||
    percent: {
 | 
			
		||||
      type: Number,
 | 
			
		||||
      default: 0,
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
let option = reactive({});
 | 
			
		||||
watch(
 | 
			
		||||
  let option = reactive({});
 | 
			
		||||
  watch(
 | 
			
		||||
    () => props.percent,
 | 
			
		||||
    () => {
 | 
			
		||||
      init();
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  init();
 | 
			
		||||
});
 | 
			
		||||
  );
 | 
			
		||||
  onMounted(() => {
 | 
			
		||||
    init();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
function init() {
 | 
			
		||||
  option = {
 | 
			
		||||
    series: [
 | 
			
		||||
      {
 | 
			
		||||
        type: "gauge",
 | 
			
		||||
        startAngle: 90,
 | 
			
		||||
        endAngle: -270,
 | 
			
		||||
        pointer: {
 | 
			
		||||
          show: false,
 | 
			
		||||
        },
 | 
			
		||||
        progress: {
 | 
			
		||||
          show: true,
 | 
			
		||||
          overlap: false,
 | 
			
		||||
          roundCap: true,
 | 
			
		||||
          clip: false,
 | 
			
		||||
          itemStyle: {
 | 
			
		||||
            borderWidth: 1,
 | 
			
		||||
            borderColor: "#464646",
 | 
			
		||||
  function init() {
 | 
			
		||||
    option = {
 | 
			
		||||
      series: [
 | 
			
		||||
        {
 | 
			
		||||
          type: 'gauge',
 | 
			
		||||
          startAngle: 90,
 | 
			
		||||
          endAngle: -270,
 | 
			
		||||
          pointer: {
 | 
			
		||||
            show: false,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        axisLine: {
 | 
			
		||||
          lineStyle: {
 | 
			
		||||
            width: 20,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        splitLine: {
 | 
			
		||||
          show: false,
 | 
			
		||||
          distance: 0,
 | 
			
		||||
          length: 10,
 | 
			
		||||
        },
 | 
			
		||||
        axisTick: {
 | 
			
		||||
          show: false,
 | 
			
		||||
        },
 | 
			
		||||
        axisLabel: {
 | 
			
		||||
          show: false,
 | 
			
		||||
          distance: 50,
 | 
			
		||||
        },
 | 
			
		||||
        data: [
 | 
			
		||||
          {
 | 
			
		||||
            value: props.percent,
 | 
			
		||||
            name: "完成度",
 | 
			
		||||
            title: {
 | 
			
		||||
              offsetCenter: ["0%", "-10%"],
 | 
			
		||||
            },
 | 
			
		||||
            detail: {
 | 
			
		||||
              offsetCenter: ["0%", "20%"],
 | 
			
		||||
          progress: {
 | 
			
		||||
            show: true,
 | 
			
		||||
            overlap: false,
 | 
			
		||||
            roundCap: true,
 | 
			
		||||
            clip: false,
 | 
			
		||||
            itemStyle: {
 | 
			
		||||
              borderWidth: 1,
 | 
			
		||||
              borderColor: '#464646',
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
        title: {
 | 
			
		||||
          fontSize: 18,
 | 
			
		||||
          axisLine: {
 | 
			
		||||
            lineStyle: {
 | 
			
		||||
              width: 20,
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
          splitLine: {
 | 
			
		||||
            show: false,
 | 
			
		||||
            distance: 0,
 | 
			
		||||
            length: 10,
 | 
			
		||||
          },
 | 
			
		||||
          axisTick: {
 | 
			
		||||
            show: false,
 | 
			
		||||
          },
 | 
			
		||||
          axisLabel: {
 | 
			
		||||
            show: false,
 | 
			
		||||
            distance: 50,
 | 
			
		||||
          },
 | 
			
		||||
          data: [
 | 
			
		||||
            {
 | 
			
		||||
              value: props.percent,
 | 
			
		||||
              name: '完成度',
 | 
			
		||||
              title: {
 | 
			
		||||
                offsetCenter: ['0%', '-10%'],
 | 
			
		||||
              },
 | 
			
		||||
              detail: {
 | 
			
		||||
                offsetCenter: ['0%', '20%'],
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          title: {
 | 
			
		||||
            fontSize: 18,
 | 
			
		||||
          },
 | 
			
		||||
          detail: {
 | 
			
		||||
            fontSize: 16,
 | 
			
		||||
            color: 'auto',
 | 
			
		||||
            formatter: '{value}%',
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        detail: {
 | 
			
		||||
          fontSize: 16,
 | 
			
		||||
          color: "auto",
 | 
			
		||||
          formatter: "{value}%",
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
  };
 | 
			
		||||
  let chartDom = document.getElementById("gauge-main");
 | 
			
		||||
  if (chartDom) {
 | 
			
		||||
    let myChart = echarts.init(chartDom);
 | 
			
		||||
    option && myChart.setOption(option);
 | 
			
		||||
      ],
 | 
			
		||||
    };
 | 
			
		||||
    let chartDom = document.getElementById('gauge-main');
 | 
			
		||||
    if (chartDom) {
 | 
			
		||||
      let myChart = echarts.init(chartDom);
 | 
			
		||||
      option && myChart.setOption(option);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="less" scoped>
 | 
			
		||||
.echarts-box {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  .gauge-main {
 | 
			
		||||
    width: 260px;
 | 
			
		||||
    height: 260px;
 | 
			
		||||
    background: #fff;
 | 
			
		||||
  .echarts-box {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    .gauge-main {
 | 
			
		||||
      width: 260px;
 | 
			
		||||
      height: 260px;
 | 
			
		||||
      background: #fff;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,210 +1,210 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <default-home-card icon="FundTwoTone" title="代码提交量">
 | 
			
		||||
  <default-home-card icon="BarChartOutlined" title="代码提交量">
 | 
			
		||||
    <div class="echarts-box">
 | 
			
		||||
      <div class="gradient-main" id="gradient-main"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </default-home-card>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup>
 | 
			
		||||
import DefaultHomeCard from "/@/views/system/home/components/default-home-card.vue";
 | 
			
		||||
import * as echarts from 'echarts';
 | 
			
		||||
import {onMounted} from "vue";
 | 
			
		||||
  import DefaultHomeCard from '/@/views/system/home/components/default-home-card.vue';
 | 
			
		||||
  import * as echarts from 'echarts';
 | 
			
		||||
  import { onMounted } from 'vue';
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  init();
 | 
			
		||||
});
 | 
			
		||||
  onMounted(() => {
 | 
			
		||||
    init();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
function init(){
 | 
			
		||||
  let option = {
 | 
			
		||||
    color: ['#80FFA5', '#00DDFF', '#37A2FF', '#FF0087', '#FFBF00'],
 | 
			
		||||
    tooltip: {
 | 
			
		||||
      trigger: 'axis',
 | 
			
		||||
      axisPointer: {
 | 
			
		||||
        type: 'cross',
 | 
			
		||||
        label: {
 | 
			
		||||
          backgroundColor: '#6a7985'
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    legend: {
 | 
			
		||||
      data: ['罗伊', '佩弦', '开云', '清野', '飞叶']
 | 
			
		||||
    },
 | 
			
		||||
    grid: {
 | 
			
		||||
      left: '3%',
 | 
			
		||||
      right: '4%',
 | 
			
		||||
      bottom: '3%',
 | 
			
		||||
      containLabel: true
 | 
			
		||||
    },
 | 
			
		||||
    xAxis: [
 | 
			
		||||
      {
 | 
			
		||||
        type: 'category',
 | 
			
		||||
        boundaryGap: false,
 | 
			
		||||
        data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    yAxis: [
 | 
			
		||||
      {
 | 
			
		||||
        type: 'value'
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    series: [
 | 
			
		||||
      {
 | 
			
		||||
        name: '罗伊',
 | 
			
		||||
        type: 'line',
 | 
			
		||||
        stack: 'Total',
 | 
			
		||||
        smooth: true,
 | 
			
		||||
        lineStyle: {
 | 
			
		||||
          width: 0
 | 
			
		||||
  function init() {
 | 
			
		||||
    let option = {
 | 
			
		||||
      color: ['#80FFA5', '#00DDFF', '#37A2FF', '#FF0087', '#FFBF00'],
 | 
			
		||||
      tooltip: {
 | 
			
		||||
        trigger: 'axis',
 | 
			
		||||
        axisPointer: {
 | 
			
		||||
          type: 'cross',
 | 
			
		||||
          label: {
 | 
			
		||||
            backgroundColor: '#6a7985',
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        showSymbol: false,
 | 
			
		||||
        areaStyle: {
 | 
			
		||||
          opacity: 0.8,
 | 
			
		||||
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
 | 
			
		||||
            {
 | 
			
		||||
              offset: 0,
 | 
			
		||||
              color: 'rgb(128, 255, 165)'
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              offset: 1,
 | 
			
		||||
              color: 'rgb(1, 191, 236)'
 | 
			
		||||
            }
 | 
			
		||||
          ])
 | 
			
		||||
        },
 | 
			
		||||
        emphasis: {
 | 
			
		||||
          focus: 'series'
 | 
			
		||||
        },
 | 
			
		||||
        data: [140, 232, 101, 264, 90, 340, 250]
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        name: '佩弦',
 | 
			
		||||
        type: 'line',
 | 
			
		||||
        stack: 'Total',
 | 
			
		||||
        smooth: true,
 | 
			
		||||
        lineStyle: {
 | 
			
		||||
          width: 0
 | 
			
		||||
        },
 | 
			
		||||
        showSymbol: false,
 | 
			
		||||
        areaStyle: {
 | 
			
		||||
          opacity: 0.8,
 | 
			
		||||
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
 | 
			
		||||
            {
 | 
			
		||||
              offset: 0,
 | 
			
		||||
              color: 'rgb(0, 221, 255)'
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              offset: 1,
 | 
			
		||||
              color: 'rgb(77, 119, 255)'
 | 
			
		||||
            }
 | 
			
		||||
          ])
 | 
			
		||||
        },
 | 
			
		||||
        emphasis: {
 | 
			
		||||
          focus: 'series'
 | 
			
		||||
        },
 | 
			
		||||
        data: [120, 282, 111, 234, 220, 340, 310]
 | 
			
		||||
      legend: {
 | 
			
		||||
        data: ['罗伊', '佩弦', '开云', '清野', '飞叶'],
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        name: '开云',
 | 
			
		||||
        type: 'line',
 | 
			
		||||
        stack: 'Total',
 | 
			
		||||
        smooth: true,
 | 
			
		||||
        lineStyle: {
 | 
			
		||||
          width: 0
 | 
			
		||||
        },
 | 
			
		||||
        showSymbol: false,
 | 
			
		||||
        areaStyle: {
 | 
			
		||||
          opacity: 0.8,
 | 
			
		||||
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
 | 
			
		||||
            {
 | 
			
		||||
              offset: 0,
 | 
			
		||||
              color: 'rgb(55, 162, 255)'
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              offset: 1,
 | 
			
		||||
              color: 'rgb(116, 21, 219)'
 | 
			
		||||
            }
 | 
			
		||||
          ])
 | 
			
		||||
        },
 | 
			
		||||
        emphasis: {
 | 
			
		||||
          focus: 'series'
 | 
			
		||||
        },
 | 
			
		||||
        data: [320, 132, 201, 334, 190, 130, 220]
 | 
			
		||||
      grid: {
 | 
			
		||||
        left: '3%',
 | 
			
		||||
        right: '4%',
 | 
			
		||||
        bottom: '3%',
 | 
			
		||||
        containLabel: true,
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        name: '清野',
 | 
			
		||||
        type: 'line',
 | 
			
		||||
        stack: 'Total',
 | 
			
		||||
        smooth: true,
 | 
			
		||||
        lineStyle: {
 | 
			
		||||
          width: 0
 | 
			
		||||
      xAxis: [
 | 
			
		||||
        {
 | 
			
		||||
          type: 'category',
 | 
			
		||||
          boundaryGap: false,
 | 
			
		||||
          data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
 | 
			
		||||
        },
 | 
			
		||||
        showSymbol: false,
 | 
			
		||||
        areaStyle: {
 | 
			
		||||
          opacity: 0.8,
 | 
			
		||||
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
 | 
			
		||||
            {
 | 
			
		||||
              offset: 0,
 | 
			
		||||
              color: 'rgb(255, 0, 135)'
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              offset: 1,
 | 
			
		||||
              color: 'rgb(135, 0, 157)'
 | 
			
		||||
            }
 | 
			
		||||
          ])
 | 
			
		||||
      ],
 | 
			
		||||
      yAxis: [
 | 
			
		||||
        {
 | 
			
		||||
          type: 'value',
 | 
			
		||||
        },
 | 
			
		||||
        emphasis: {
 | 
			
		||||
          focus: 'series'
 | 
			
		||||
      ],
 | 
			
		||||
      series: [
 | 
			
		||||
        {
 | 
			
		||||
          name: '罗伊',
 | 
			
		||||
          type: 'line',
 | 
			
		||||
          stack: 'Total',
 | 
			
		||||
          smooth: true,
 | 
			
		||||
          lineStyle: {
 | 
			
		||||
            width: 0,
 | 
			
		||||
          },
 | 
			
		||||
          showSymbol: false,
 | 
			
		||||
          areaStyle: {
 | 
			
		||||
            opacity: 0.8,
 | 
			
		||||
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
 | 
			
		||||
              {
 | 
			
		||||
                offset: 0,
 | 
			
		||||
                color: 'rgb(128, 255, 165)',
 | 
			
		||||
              },
 | 
			
		||||
              {
 | 
			
		||||
                offset: 1,
 | 
			
		||||
                color: 'rgb(1, 191, 236)',
 | 
			
		||||
              },
 | 
			
		||||
            ]),
 | 
			
		||||
          },
 | 
			
		||||
          emphasis: {
 | 
			
		||||
            focus: 'series',
 | 
			
		||||
          },
 | 
			
		||||
          data: [140, 232, 101, 264, 90, 340, 250],
 | 
			
		||||
        },
 | 
			
		||||
        data: [220, 402, 231, 134, 190, 230, 120]
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        name: '飞叶',
 | 
			
		||||
        type: 'line',
 | 
			
		||||
        stack: 'Total',
 | 
			
		||||
        smooth: true,
 | 
			
		||||
        lineStyle: {
 | 
			
		||||
          width: 0
 | 
			
		||||
        {
 | 
			
		||||
          name: '佩弦',
 | 
			
		||||
          type: 'line',
 | 
			
		||||
          stack: 'Total',
 | 
			
		||||
          smooth: true,
 | 
			
		||||
          lineStyle: {
 | 
			
		||||
            width: 0,
 | 
			
		||||
          },
 | 
			
		||||
          showSymbol: false,
 | 
			
		||||
          areaStyle: {
 | 
			
		||||
            opacity: 0.8,
 | 
			
		||||
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
 | 
			
		||||
              {
 | 
			
		||||
                offset: 0,
 | 
			
		||||
                color: 'rgb(0, 221, 255)',
 | 
			
		||||
              },
 | 
			
		||||
              {
 | 
			
		||||
                offset: 1,
 | 
			
		||||
                color: 'rgb(77, 119, 255)',
 | 
			
		||||
              },
 | 
			
		||||
            ]),
 | 
			
		||||
          },
 | 
			
		||||
          emphasis: {
 | 
			
		||||
            focus: 'series',
 | 
			
		||||
          },
 | 
			
		||||
          data: [120, 282, 111, 234, 220, 340, 310],
 | 
			
		||||
        },
 | 
			
		||||
        showSymbol: false,
 | 
			
		||||
        label: {
 | 
			
		||||
          show: true,
 | 
			
		||||
          position: 'top'
 | 
			
		||||
        {
 | 
			
		||||
          name: '开云',
 | 
			
		||||
          type: 'line',
 | 
			
		||||
          stack: 'Total',
 | 
			
		||||
          smooth: true,
 | 
			
		||||
          lineStyle: {
 | 
			
		||||
            width: 0,
 | 
			
		||||
          },
 | 
			
		||||
          showSymbol: false,
 | 
			
		||||
          areaStyle: {
 | 
			
		||||
            opacity: 0.8,
 | 
			
		||||
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
 | 
			
		||||
              {
 | 
			
		||||
                offset: 0,
 | 
			
		||||
                color: 'rgb(55, 162, 255)',
 | 
			
		||||
              },
 | 
			
		||||
              {
 | 
			
		||||
                offset: 1,
 | 
			
		||||
                color: 'rgb(116, 21, 219)',
 | 
			
		||||
              },
 | 
			
		||||
            ]),
 | 
			
		||||
          },
 | 
			
		||||
          emphasis: {
 | 
			
		||||
            focus: 'series',
 | 
			
		||||
          },
 | 
			
		||||
          data: [320, 132, 201, 334, 190, 130, 220],
 | 
			
		||||
        },
 | 
			
		||||
        areaStyle: {
 | 
			
		||||
          opacity: 0.8,
 | 
			
		||||
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
 | 
			
		||||
            {
 | 
			
		||||
              offset: 0,
 | 
			
		||||
              color: 'rgb(255, 191, 0)'
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              offset: 1,
 | 
			
		||||
              color: 'rgb(224, 62, 76)'
 | 
			
		||||
            }
 | 
			
		||||
          ])
 | 
			
		||||
        {
 | 
			
		||||
          name: '清野',
 | 
			
		||||
          type: 'line',
 | 
			
		||||
          stack: 'Total',
 | 
			
		||||
          smooth: true,
 | 
			
		||||
          lineStyle: {
 | 
			
		||||
            width: 0,
 | 
			
		||||
          },
 | 
			
		||||
          showSymbol: false,
 | 
			
		||||
          areaStyle: {
 | 
			
		||||
            opacity: 0.8,
 | 
			
		||||
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
 | 
			
		||||
              {
 | 
			
		||||
                offset: 0,
 | 
			
		||||
                color: 'rgb(255, 0, 135)',
 | 
			
		||||
              },
 | 
			
		||||
              {
 | 
			
		||||
                offset: 1,
 | 
			
		||||
                color: 'rgb(135, 0, 157)',
 | 
			
		||||
              },
 | 
			
		||||
            ]),
 | 
			
		||||
          },
 | 
			
		||||
          emphasis: {
 | 
			
		||||
            focus: 'series',
 | 
			
		||||
          },
 | 
			
		||||
          data: [220, 402, 231, 134, 190, 230, 120],
 | 
			
		||||
        },
 | 
			
		||||
        emphasis: {
 | 
			
		||||
          focus: 'series'
 | 
			
		||||
        {
 | 
			
		||||
          name: '飞叶',
 | 
			
		||||
          type: 'line',
 | 
			
		||||
          stack: 'Total',
 | 
			
		||||
          smooth: true,
 | 
			
		||||
          lineStyle: {
 | 
			
		||||
            width: 0,
 | 
			
		||||
          },
 | 
			
		||||
          showSymbol: false,
 | 
			
		||||
          label: {
 | 
			
		||||
            show: true,
 | 
			
		||||
            position: 'top',
 | 
			
		||||
          },
 | 
			
		||||
          areaStyle: {
 | 
			
		||||
            opacity: 0.8,
 | 
			
		||||
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
 | 
			
		||||
              {
 | 
			
		||||
                offset: 0,
 | 
			
		||||
                color: 'rgb(255, 191, 0)',
 | 
			
		||||
              },
 | 
			
		||||
              {
 | 
			
		||||
                offset: 1,
 | 
			
		||||
                color: 'rgb(224, 62, 76)',
 | 
			
		||||
              },
 | 
			
		||||
            ]),
 | 
			
		||||
          },
 | 
			
		||||
          emphasis: {
 | 
			
		||||
            focus: 'series',
 | 
			
		||||
          },
 | 
			
		||||
          data: [220, 302, 181, 234, 210, 290, 150],
 | 
			
		||||
        },
 | 
			
		||||
        data: [220, 302, 181, 234, 210, 290, 150]
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  };
 | 
			
		||||
  let chartDom = document.getElementById("gradient-main");
 | 
			
		||||
  if (chartDom) {
 | 
			
		||||
    let myChart = echarts.init(chartDom);
 | 
			
		||||
    option && myChart.setOption(option);
 | 
			
		||||
      ],
 | 
			
		||||
    };
 | 
			
		||||
    let chartDom = document.getElementById('gradient-main');
 | 
			
		||||
    if (chartDom) {
 | 
			
		||||
      let myChart = echarts.init(chartDom);
 | 
			
		||||
      option && myChart.setOption(option);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
<style lang='less' scoped>
 | 
			
		||||
.echarts-box {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  .gradient-main {
 | 
			
		||||
    width: 1200px;
 | 
			
		||||
    height: 300px;
 | 
			
		||||
    background: #fff;
 | 
			
		||||
<style lang="less" scoped>
 | 
			
		||||
  .echarts-box {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    .gradient-main {
 | 
			
		||||
      width: 1200px;
 | 
			
		||||
      height: 300px;
 | 
			
		||||
      background: #fff;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,78 +1,78 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <default-home-card icon="PieChartTwoTone" title="加班统计">
 | 
			
		||||
  <default-home-card icon="PieChartOutlined" title="加班统计">
 | 
			
		||||
    <div class="echarts-box">
 | 
			
		||||
      <div class="pie-main" id="pie-main"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </default-home-card>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup>
 | 
			
		||||
import DefaultHomeCard from "/@/views/system/home/components/default-home-card.vue";
 | 
			
		||||
import * as echarts from 'echarts';
 | 
			
		||||
import {onMounted} from "vue";
 | 
			
		||||
  import DefaultHomeCard from '/@/views/system/home/components/default-home-card.vue';
 | 
			
		||||
  import * as echarts from 'echarts';
 | 
			
		||||
  import { onMounted } from 'vue';
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  init();
 | 
			
		||||
});
 | 
			
		||||
  onMounted(() => {
 | 
			
		||||
    init();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
function init(){
 | 
			
		||||
  let option = {
 | 
			
		||||
    tooltip: {
 | 
			
		||||
      trigger: 'item'
 | 
			
		||||
    },
 | 
			
		||||
    legend: {
 | 
			
		||||
      top: '5%',
 | 
			
		||||
      left: 'center'
 | 
			
		||||
    },
 | 
			
		||||
    series: [
 | 
			
		||||
      {
 | 
			
		||||
        name: '加班次数',
 | 
			
		||||
        type: 'pie',
 | 
			
		||||
        radius: ['40%', '70%'],
 | 
			
		||||
        avoidLabelOverlap: false,
 | 
			
		||||
        itemStyle: {
 | 
			
		||||
          borderRadius: 10,
 | 
			
		||||
          borderColor: '#fff',
 | 
			
		||||
          borderWidth: 2
 | 
			
		||||
        },
 | 
			
		||||
        label: {
 | 
			
		||||
          show: false,
 | 
			
		||||
          position: 'center'
 | 
			
		||||
        },
 | 
			
		||||
        emphasis: {
 | 
			
		||||
  function init() {
 | 
			
		||||
    let option = {
 | 
			
		||||
      tooltip: {
 | 
			
		||||
        trigger: 'item',
 | 
			
		||||
      },
 | 
			
		||||
      legend: {
 | 
			
		||||
        top: '5%',
 | 
			
		||||
        left: 'center',
 | 
			
		||||
      },
 | 
			
		||||
      series: [
 | 
			
		||||
        {
 | 
			
		||||
          name: '加班次数',
 | 
			
		||||
          type: 'pie',
 | 
			
		||||
          radius: ['40%', '70%'],
 | 
			
		||||
          avoidLabelOverlap: false,
 | 
			
		||||
          itemStyle: {
 | 
			
		||||
            borderRadius: 10,
 | 
			
		||||
            borderColor: '#fff',
 | 
			
		||||
            borderWidth: 2,
 | 
			
		||||
          },
 | 
			
		||||
          label: {
 | 
			
		||||
            show: true,
 | 
			
		||||
            fontSize: '40',
 | 
			
		||||
            fontWeight: 'bold'
 | 
			
		||||
          }
 | 
			
		||||
            show: false,
 | 
			
		||||
            position: 'center',
 | 
			
		||||
          },
 | 
			
		||||
          emphasis: {
 | 
			
		||||
            label: {
 | 
			
		||||
              show: true,
 | 
			
		||||
              fontSize: '40',
 | 
			
		||||
              fontWeight: 'bold',
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
          labelLine: {
 | 
			
		||||
            show: false,
 | 
			
		||||
          },
 | 
			
		||||
          data: [
 | 
			
		||||
            { value: 10, name: '初晓' },
 | 
			
		||||
            { value: 8, name: '善逸' },
 | 
			
		||||
            { value: 3, name: '胡克' },
 | 
			
		||||
            { value: 1, name: '罗伊' },
 | 
			
		||||
          ],
 | 
			
		||||
        },
 | 
			
		||||
        labelLine: {
 | 
			
		||||
          show: false
 | 
			
		||||
        },
 | 
			
		||||
        data: [
 | 
			
		||||
          { value: 10, name: '初晓' },
 | 
			
		||||
          { value: 8, name: '善逸' },
 | 
			
		||||
          { value: 3, name: '胡克' },
 | 
			
		||||
          { value: 1, name: '罗伊' },
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  };
 | 
			
		||||
  let chartDom = document.getElementById("pie-main");
 | 
			
		||||
  if (chartDom) {
 | 
			
		||||
    let myChart = echarts.init(chartDom);
 | 
			
		||||
    option && myChart.setOption(option);
 | 
			
		||||
      ],
 | 
			
		||||
    };
 | 
			
		||||
    let chartDom = document.getElementById('pie-main');
 | 
			
		||||
    if (chartDom) {
 | 
			
		||||
      let myChart = echarts.init(chartDom);
 | 
			
		||||
      option && myChart.setOption(option);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
<style lang='less' scoped>
 | 
			
		||||
.echarts-box {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  .pie-main {
 | 
			
		||||
    width: 260px;
 | 
			
		||||
    height: 260px;
 | 
			
		||||
    background: #fff;
 | 
			
		||||
<style lang="less" scoped>
 | 
			
		||||
  .echarts-box {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    .pie-main {
 | 
			
		||||
      width: 260px;
 | 
			
		||||
      height: 260px;
 | 
			
		||||
      background: #fff;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
  *
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <default-home-card icon="SmileTwoTone" title="联系我们">
 | 
			
		||||
  <default-home-card icon="SmileOutlined" title="联系我们">
 | 
			
		||||
    <div class="app-qr-box">
 | 
			
		||||
      <div class="app-qr">
 | 
			
		||||
        <img :src="zhuoda" />
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,12 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <default-home-card
 | 
			
		||||
      :extra="`${editFlag ? '完成' : '编辑'}`"
 | 
			
		||||
      icon="ThunderboltTwoTone"
 | 
			
		||||
      title="快捷入口"
 | 
			
		||||
      @extraClick="editFlag = !editFlag"
 | 
			
		||||
  >
 | 
			
		||||
  <default-home-card :extra="`${editFlag ? '完成' : '编辑'}`" icon="ThunderboltTwoTone" title="快捷入口" @extraClick="editFlag = !editFlag">
 | 
			
		||||
    <div class="quick-entry-list">
 | 
			
		||||
      <a-row>
 | 
			
		||||
        <a-col v-for="(item,index) in quickEntry" :key="index" span="4">
 | 
			
		||||
        <a-col v-for="(item, index) in quickEntry" :key="index" span="4">
 | 
			
		||||
          <div class="quick-entry" @click="turnToPage(item.path)">
 | 
			
		||||
            <div class="icon">
 | 
			
		||||
              <component :is='$antIcons[item.icon]' :style="{ fontSize:'30px'}"/>
 | 
			
		||||
              <close-circle-outlined v-if="editFlag" class="delete-icon" @click="deleteQuickEntry(index)"/>
 | 
			
		||||
              <component :is="$antIcons[item.icon]" :style="{ fontSize: '30px' }" />
 | 
			
		||||
              <close-circle-outlined v-if="editFlag" class="delete-icon" @click="deleteQuickEntry(index)" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <span class="entry-title">{{ item.title }}</span>
 | 
			
		||||
          </div>
 | 
			
		||||
@@ -19,131 +14,135 @@
 | 
			
		||||
        <a-col v-if="editFlag && quickEntry.length < maxCount" span="4">
 | 
			
		||||
          <div class="add-quick-entry" @click="addHomeQuickEntry">
 | 
			
		||||
            <div class="add-icon">
 | 
			
		||||
              <plus-outlined :style="{ fontSize:'30px'}"/>
 | 
			
		||||
              <plus-outlined :style="{ fontSize: '30px' }" />
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </a-col>
 | 
			
		||||
      </a-row>
 | 
			
		||||
    </div>
 | 
			
		||||
  </default-home-card>
 | 
			
		||||
  <HomeQuickEntryModal ref="homeQuickEntryModal" @addQuickEntry="addQuickEntry"/>
 | 
			
		||||
  <HomeQuickEntryModal ref="homeQuickEntryModal" @addQuickEntry="addQuickEntry" />
 | 
			
		||||
</template>
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, ref} from "vue";
 | 
			
		||||
import {router} from "/@/router";
 | 
			
		||||
import HomeQuickEntryModal from './home-quick-entry-modal.vue'
 | 
			
		||||
import localKey from '/@/constants/local-storage-key-const';
 | 
			
		||||
import {localRead, localSave} from '/@/utils/local-util';
 | 
			
		||||
import _ from "lodash";
 | 
			
		||||
import InitQuickEntryList from './init-quick-entry-list';
 | 
			
		||||
import DefaultHomeCard from "/@/views/system/home/components/default-home-card.vue";
 | 
			
		||||
  import { onMounted, ref } from 'vue';
 | 
			
		||||
  import { router } from '/@/router';
 | 
			
		||||
  import HomeQuickEntryModal from './home-quick-entry-modal.vue';
 | 
			
		||||
  import localKey from '/@/constants/local-storage-key-const';
 | 
			
		||||
  import { localRead, localSave } from '/@/utils/local-util';
 | 
			
		||||
  import _ from 'lodash';
 | 
			
		||||
  import InitQuickEntryList from './init-quick-entry-list';
 | 
			
		||||
  import DefaultHomeCard from '/@/views/system/home/components/default-home-card.vue';
 | 
			
		||||
  import { theme } from 'ant-design-vue';
 | 
			
		||||
 | 
			
		||||
//---------------- 初始化展示 --------------------
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  initQuickEntry();
 | 
			
		||||
})
 | 
			
		||||
let quickEntry = ref([])
 | 
			
		||||
  //---------------- 初始化展示 --------------------
 | 
			
		||||
  onMounted(() => {
 | 
			
		||||
    initQuickEntry();
 | 
			
		||||
  });
 | 
			
		||||
  let quickEntry = ref([]);
 | 
			
		||||
 | 
			
		||||
function initQuickEntry() {
 | 
			
		||||
  let quickEntryJson = localRead(localKey.HOME_QUICK_ENTRY);
 | 
			
		||||
  if (!quickEntryJson) {
 | 
			
		||||
    quickEntry.value = _.cloneDeep(InitQuickEntryList);
 | 
			
		||||
    return;
 | 
			
		||||
  function initQuickEntry() {
 | 
			
		||||
    let quickEntryJson = localRead(localKey.HOME_QUICK_ENTRY);
 | 
			
		||||
    if (!quickEntryJson) {
 | 
			
		||||
      quickEntry.value = _.cloneDeep(InitQuickEntryList);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    let quickEntryList = JSON.parse(quickEntryJson);
 | 
			
		||||
    if (_.isEmpty(quickEntryList)) {
 | 
			
		||||
      quickEntry.value = _.cloneDeep(InitQuickEntryList);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    quickEntry.value = quickEntryList;
 | 
			
		||||
  }
 | 
			
		||||
  let quickEntryList = JSON.parse(quickEntryJson);
 | 
			
		||||
  if (_.isEmpty(quickEntryList)) {
 | 
			
		||||
    quickEntry.value = _.cloneDeep(InitQuickEntryList);
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  // 页面跳转
 | 
			
		||||
  function turnToPage(path) {
 | 
			
		||||
    if (editFlag.value) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    router.push({ path });
 | 
			
		||||
  }
 | 
			
		||||
  quickEntry.value = quickEntryList;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 页面跳转
 | 
			
		||||
function turnToPage(path) {
 | 
			
		||||
  if (editFlag.value) {
 | 
			
		||||
    return;
 | 
			
		||||
  //----------------  编辑快捷入口 --------------------
 | 
			
		||||
  let editFlag = ref(false);
 | 
			
		||||
  let maxCount = ref(6);
 | 
			
		||||
 | 
			
		||||
  // 快捷入口删除
 | 
			
		||||
  function deleteQuickEntry(index) {
 | 
			
		||||
    quickEntry.value.splice(index, 1);
 | 
			
		||||
    localSave(localKey.HOME_QUICK_ENTRY, JSON.stringify(quickEntry.value));
 | 
			
		||||
  }
 | 
			
		||||
  router.push({path});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//----------------  编辑快捷入口 --------------------
 | 
			
		||||
let editFlag = ref(false);
 | 
			
		||||
let maxCount = ref(6);
 | 
			
		||||
  // 添加快捷入口
 | 
			
		||||
  let homeQuickEntryModal = ref();
 | 
			
		||||
 | 
			
		||||
// 快捷入口删除
 | 
			
		||||
function deleteQuickEntry(index) {
 | 
			
		||||
  quickEntry.value.splice(index, 1)
 | 
			
		||||
  localSave(localKey.HOME_QUICK_ENTRY, JSON.stringify(quickEntry.value));
 | 
			
		||||
}
 | 
			
		||||
  function addHomeQuickEntry() {
 | 
			
		||||
    homeQuickEntryModal.value.showModal();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
// 添加快捷入口
 | 
			
		||||
let homeQuickEntryModal = ref();
 | 
			
		||||
  function addQuickEntry(row) {
 | 
			
		||||
    quickEntry.value.push(row);
 | 
			
		||||
    localSave(localKey.HOME_QUICK_ENTRY, JSON.stringify(quickEntry.value));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
function addHomeQuickEntry() {
 | 
			
		||||
  homeQuickEntryModal.value.showModal();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function addQuickEntry(row) {
 | 
			
		||||
  quickEntry.value.push(row);
 | 
			
		||||
  localSave(localKey.HOME_QUICK_ENTRY, JSON.stringify(quickEntry.value));
 | 
			
		||||
}
 | 
			
		||||
  const { useToken } = theme;
 | 
			
		||||
  const { token } = useToken();
 | 
			
		||||
</script>
 | 
			
		||||
<style lang='less' scoped>
 | 
			
		||||
.quick-entry-list {
 | 
			
		||||
  height: 100%;
 | 
			
		||||
<style lang="less" scoped>
 | 
			
		||||
  .quick-entry-list {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
 | 
			
		||||
  .quick-entry {
 | 
			
		||||
    padding: 10px 0;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
 | 
			
		||||
    .entry-title {
 | 
			
		||||
      margin-top: 5px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .icon {
 | 
			
		||||
      position: relative;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
      background-color: #F0FFFF;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .delete-icon {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      color: #F08080;
 | 
			
		||||
      top: -5px;
 | 
			
		||||
      right: -5px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .add-quick-entry {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
 | 
			
		||||
    .add-icon {
 | 
			
		||||
      width: 70px;
 | 
			
		||||
      height: 70px;
 | 
			
		||||
      background-color: #fafafa;
 | 
			
		||||
      border: 1px dashed #d9d9d9;
 | 
			
		||||
      border-radius: 2px;
 | 
			
		||||
    .quick-entry {
 | 
			
		||||
      padding: 10px 0;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      flex-direction: column;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      justify-content: center;
 | 
			
		||||
      cursor: pointer;
 | 
			
		||||
      transition: border-color .3s;
 | 
			
		||||
      border-radius: 4px;
 | 
			
		||||
 | 
			
		||||
      .entry-title {
 | 
			
		||||
        margin-top: 5px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .icon {
 | 
			
		||||
        position: relative;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      &:hover {
 | 
			
		||||
        background-color: #f0ffff;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .delete-icon {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        color: #f08080;
 | 
			
		||||
        top: -5px;
 | 
			
		||||
        right: -5px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .add-quick-entry {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      justify-content: center;
 | 
			
		||||
      color: #A9A9A9;
 | 
			
		||||
 | 
			
		||||
      &:hover {
 | 
			
		||||
        border-color: @primary-color;
 | 
			
		||||
        color: @primary-color;
 | 
			
		||||
      .add-icon {
 | 
			
		||||
        width: 70px;
 | 
			
		||||
        height: 70px;
 | 
			
		||||
        background-color: #fafafa;
 | 
			
		||||
        border: 1px dashed #d9d9d9;
 | 
			
		||||
        border-radius: 2px;
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
        transition: border-color 0.3s;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        justify-content: center;
 | 
			
		||||
        color: #a9a9a9;
 | 
			
		||||
 | 
			
		||||
        &:hover {
 | 
			
		||||
          border-color: v-bind('token.colorPrimary');
 | 
			
		||||
          color: v-bind('token.colorPrimary');
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,8 @@
 | 
			
		||||
  *
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <default-home-card icon="StarTwoTone" title="已办待办">
 | 
			
		||||
    <div style="height: 280px;">
 | 
			
		||||
  <default-home-card icon="Star" title="已办待办">
 | 
			
		||||
    <div style="height: 280px">
 | 
			
		||||
      <div class="center column">
 | 
			
		||||
        <a-space direction="vertical" style="width: 100%">
 | 
			
		||||
          <div v-for="(item, index) in toDoList" :key="index" :class="['to-do', { done: item.doneFlag }]">
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
  *
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <default-home-card extra="更多" icon="SoundTwoTone" title="通知公告" @extraClick="onMore">
 | 
			
		||||
  <default-home-card extra="更多" icon="SoundOutlined" title="通知公告" @extraClick="onMore">
 | 
			
		||||
    <a-spin :spinning="loading">
 | 
			
		||||
      <div class="content-wrapper">
 | 
			
		||||
        <a-empty v-if="$lodash.isEmpty(data)" />
 | 
			
		||||
@@ -37,10 +37,6 @@
 | 
			
		||||
  import { noticeApi } from '/@/api/business/oa/notice-api';
 | 
			
		||||
  import { smartSentry } from '/@/lib/smart-sentry';
 | 
			
		||||
  import DefaultHomeCard from '/@/views/system/home/components/default-home-card.vue';
 | 
			
		||||
  import { theme } from 'ant-design-vue';
 | 
			
		||||
  const { useToken } = theme;
 | 
			
		||||
  const { token } = useToken();
 | 
			
		||||
  const colorPrimary = token.value.colorPrimary;
 | 
			
		||||
 | 
			
		||||
  const props = defineProps({
 | 
			
		||||
    noticeTypeId: {
 | 
			
		||||
@@ -110,7 +106,6 @@
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
      word-break: break-all;
 | 
			
		||||
      margin-right: 5px;
 | 
			
		||||
      color: v-bind(colorPrimary);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .time {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
.login-container {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  background: url(/@/assets/images/login/login-bg.jpg) no-repeat center;
 | 
			
		||||
  background: url(/@/assets/images/login/login-bg.png) no-repeat center;
 | 
			
		||||
  background-size: cover;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
@@ -20,6 +20,7 @@
 | 
			
		||||
      border-radius: 0px 12px 12px 0px;
 | 
			
		||||
      padding: 34px 42px;
 | 
			
		||||
      position: relative;
 | 
			
		||||
      border: solid 1px #efefef;
 | 
			
		||||
    }
 | 
			
		||||
    .login-qr {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
@@ -160,7 +161,7 @@
 | 
			
		||||
    .btn {
 | 
			
		||||
      width: 350px;
 | 
			
		||||
      height: 50px;
 | 
			
		||||
      background: #1890ff;
 | 
			
		||||
      background: #1748FD;
 | 
			
		||||
      border-radius: 4px;
 | 
			
		||||
      font-size: 16px;
 | 
			
		||||
      font-weight: 700;
 | 
			
		||||
@@ -193,14 +194,14 @@
 | 
			
		||||
      margin: 0 19px;
 | 
			
		||||
    }
 | 
			
		||||
    .login-type {
 | 
			
		||||
      padding: 0 50px;
 | 
			
		||||
      padding: 0 5px;
 | 
			
		||||
      margin-top: 25px;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      justify-content: space-between;
 | 
			
		||||
      > img {
 | 
			
		||||
        width: 22px;
 | 
			
		||||
        height: 22px;
 | 
			
		||||
        width: 30px;
 | 
			
		||||
        height: 30px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -86,38 +86,37 @@
 | 
			
		||||
          <p class="line"></p>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="login-type">
 | 
			
		||||
          <img :src="aliLogin" />
 | 
			
		||||
          <img :src="qqLogin" />
 | 
			
		||||
          <img :src="googleLogin" />
 | 
			
		||||
          <img :src="weiboLogin" />
 | 
			
		||||
          <img src="/@/assets/images/login/wechat-icon.png" />
 | 
			
		||||
          <img src="/@/assets/images/login/ali-icon.png" />
 | 
			
		||||
          <img src="/@/assets/images/login/douyin-icon.png" />
 | 
			
		||||
          <img src="/@/assets/images/login/qq-icon.png" />
 | 
			
		||||
          <img src="/@/assets/images/login/weibo-icon.png" />
 | 
			
		||||
          <img src="/@/assets/images/login/feishu-icon.png" />
 | 
			
		||||
          <img src="/@/assets/images/login/google-icon.png" />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup>
 | 
			
		||||
  import { message } from 'ant-design-vue';
 | 
			
		||||
  import { message, notification, Button } from 'ant-design-vue';
 | 
			
		||||
  import { onMounted, onUnmounted, reactive, ref } from 'vue';
 | 
			
		||||
  import { useRouter } from 'vue-router';
 | 
			
		||||
  import { loginApi } from '/@/api/system/login-api';
 | 
			
		||||
  import { SmartLoading } from '/@/components/framework/smart-loading';
 | 
			
		||||
  import { LOGIN_DEVICE_ENUM } from '/@/constants/system/login-device-const';
 | 
			
		||||
  import { useUserStore } from '/@/store/modules/system/user';
 | 
			
		||||
  import { saveTokenToCookie } from '/@/utils/cookie-util';
 | 
			
		||||
 | 
			
		||||
  import gongzhonghao from '/@/assets/images/1024lab/1024lab-gzh.jpg';
 | 
			
		||||
  import zhuoda from '/@/assets/images/1024lab/zhuoda-wechat.jpg';
 | 
			
		||||
  import loginQR from '/@/assets/images/login/login-qr.png';
 | 
			
		||||
  import gzh from '/@/assets/images/1024lab/gzh.jpg';
 | 
			
		||||
 | 
			
		||||
  import aliLogin from '/@/assets/images/login/ali-icon.png';
 | 
			
		||||
  import googleLogin from '/@/assets/images/login/google-icon.png';
 | 
			
		||||
  import qqLogin from '/@/assets/images/login/qq-icon.png';
 | 
			
		||||
  import weiboLogin from '/@/assets/images/login/weibo-icon.png';
 | 
			
		||||
 | 
			
		||||
  import { buildRoutes } from '/@/router/index';
 | 
			
		||||
  import { smartSentry } from '/@/lib/smart-sentry';
 | 
			
		||||
  import { encryptData } from '/@/lib/encrypt';
 | 
			
		||||
  import { h } from 'vue';
 | 
			
		||||
  import { localSave } from '/@/utils/local-util.js';
 | 
			
		||||
  import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
 | 
			
		||||
 | 
			
		||||
  //--------------------- 登录表单 ---------------------------------
 | 
			
		||||
 | 
			
		||||
@@ -145,6 +144,25 @@
 | 
			
		||||
        onLogin();
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    notification['success']({
 | 
			
		||||
      message: '温馨提示',
 | 
			
		||||
      description: 'SmartAdmin 提供 9种 登录背景风格哦!',
 | 
			
		||||
      duration: null,
 | 
			
		||||
      onClick: () => {},
 | 
			
		||||
      btn: () =>
 | 
			
		||||
        h(
 | 
			
		||||
          Button,
 | 
			
		||||
          {
 | 
			
		||||
            type: 'primary',
 | 
			
		||||
            target: '_blank',
 | 
			
		||||
            size: 'small',
 | 
			
		||||
            href: 'https://smartadmin.vip/views/v3/front/Login.html',
 | 
			
		||||
            onClick: () => {},
 | 
			
		||||
          },
 | 
			
		||||
          { default: () => '去看看' }
 | 
			
		||||
        ),
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  onUnmounted(() => {
 | 
			
		||||
@@ -162,7 +180,7 @@
 | 
			
		||||
        });
 | 
			
		||||
        const res = await loginApi.login(encryptPasswordForm);
 | 
			
		||||
        stopRefrestCaptchaInterval();
 | 
			
		||||
        saveTokenToCookie(res.data.token ? res.data.token : '');
 | 
			
		||||
        localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
 | 
			
		||||
        message.success('登录成功');
 | 
			
		||||
        //更新用户信息到pinia
 | 
			
		||||
        useUserStore().setUserLoginInfo(res.data);
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,196 @@
 | 
			
		||||
.login-container {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  background: url(/@/assets/images/login/login-bg.png) no-repeat center;
 | 
			
		||||
  background-size: cover;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  .box-item {
 | 
			
		||||
    width: 444px;
 | 
			
		||||
    height: 570px;
 | 
			
		||||
    &.desc {
 | 
			
		||||
      background: #ffffff;
 | 
			
		||||
      border-radius: 12px 0px 0px 12px;
 | 
			
		||||
      box-shadow: 0px 16px 73px 8px rgba(203, 203, 203, 0.2);
 | 
			
		||||
      padding: 23px 25px;
 | 
			
		||||
    }
 | 
			
		||||
    &.login {
 | 
			
		||||
      background: #ffffff;
 | 
			
		||||
      border-radius: 0px 12px 12px 0px;
 | 
			
		||||
      padding: 34px 42px;
 | 
			
		||||
      position: relative;
 | 
			
		||||
    }
 | 
			
		||||
    .login-qr {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      top: 0;
 | 
			
		||||
      right: 0;
 | 
			
		||||
      width: 66px;
 | 
			
		||||
      height: 66px;
 | 
			
		||||
    }
 | 
			
		||||
    .welcome {
 | 
			
		||||
      background: url(/@/assets/images/login/left-bg1.png) no-repeat center;
 | 
			
		||||
      background-size: cover;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      border-radius: 8px;
 | 
			
		||||
      box-shadow: 0px 6px 20px 0px rgba(33,47,117,0.10);
 | 
			
		||||
      padding-top: 35px;
 | 
			
		||||
      p{
 | 
			
		||||
        color: #333333;
 | 
			
		||||
        line-height: 25px;
 | 
			
		||||
        letter-spacing: 0.26px;
 | 
			
		||||
        text-align: center;
 | 
			
		||||
        font-weight: 700;
 | 
			
		||||
        font-size: 26px;
 | 
			
		||||
      }
 | 
			
		||||
      .sub-welcome{
 | 
			
		||||
        color: #333333;
 | 
			
		||||
        line-height: 25px;
 | 
			
		||||
        letter-spacing: 0.26px;
 | 
			
		||||
        font-weight: 700;
 | 
			
		||||
        font-size: 20px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    .app-qr-box {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      justify-content: space-around;
 | 
			
		||||
      margin-top: 20px;
 | 
			
		||||
      .app-qr {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        justify-content: center;
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        width: 50%;
 | 
			
		||||
        > img {
 | 
			
		||||
          width: 112px;
 | 
			
		||||
          height: 112px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .qr-desc {
 | 
			
		||||
          display: flex;
 | 
			
		||||
          align-items: center;
 | 
			
		||||
          margin-top: 11px;
 | 
			
		||||
          font-size: 12px;
 | 
			
		||||
          font-weight: 500;
 | 
			
		||||
          text-align: center;
 | 
			
		||||
          color: #ffffff;
 | 
			
		||||
          > img {
 | 
			
		||||
            width: 15px;
 | 
			
		||||
            height: 18px;
 | 
			
		||||
            margin-right: 9px;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .qr-desc-marquee {
 | 
			
		||||
          width: 100%;
 | 
			
		||||
          display: flex;
 | 
			
		||||
          align-items: center;
 | 
			
		||||
          margin-top: 11px;
 | 
			
		||||
          font-size: 12px;
 | 
			
		||||
          font-weight: 500;
 | 
			
		||||
          text-align: center;
 | 
			
		||||
          color: #ffffff;
 | 
			
		||||
 | 
			
		||||
          @keyframes marquee {
 | 
			
		||||
            0% {
 | 
			
		||||
              transform: translateX(0);
 | 
			
		||||
            }
 | 
			
		||||
            100% {
 | 
			
		||||
              transform: translateX(-30%);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .marquee {
 | 
			
		||||
            flex: 1;
 | 
			
		||||
            overflow: hidden;
 | 
			
		||||
            span {
 | 
			
		||||
              display: inline-block;
 | 
			
		||||
              width: 100%;
 | 
			
		||||
              white-space: nowrap;
 | 
			
		||||
              animation: marquee 5s linear infinite;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    .login-title {
 | 
			
		||||
      font-size: 20px;
 | 
			
		||||
      text-align: center;
 | 
			
		||||
      color: #1e1e1e;
 | 
			
		||||
      margin-bottom: 35px;
 | 
			
		||||
    }
 | 
			
		||||
    .login-form {
 | 
			
		||||
      .captcha-input {
 | 
			
		||||
        width: 60%;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .captcha-img {
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    .ant-input,
 | 
			
		||||
    .ant-input-affix-wrapper {
 | 
			
		||||
      height: 44px;
 | 
			
		||||
      border: 1px solid #ededed;
 | 
			
		||||
      border-radius: 4px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .eye-box {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      right: 15px;
 | 
			
		||||
      top: 10px;
 | 
			
		||||
      .eye-icon {
 | 
			
		||||
        width: 20px;
 | 
			
		||||
        height: 20px;
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    .btn {
 | 
			
		||||
      width: 350px;
 | 
			
		||||
      height: 50px;
 | 
			
		||||
      background: #1748FD;
 | 
			
		||||
      border-radius: 4px;
 | 
			
		||||
      font-size: 16px;
 | 
			
		||||
      font-weight: 700;
 | 
			
		||||
      text-align: center;
 | 
			
		||||
      color: #ffffff;
 | 
			
		||||
      line-height: 50px;
 | 
			
		||||
      cursor: pointer;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .more {
 | 
			
		||||
    margin-top: 30px;
 | 
			
		||||
    .title-box {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      justify-content: center;
 | 
			
		||||
      > p {
 | 
			
		||||
        margin-bottom: 0;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    .line {
 | 
			
		||||
      width: 114px;
 | 
			
		||||
      height: 1px;
 | 
			
		||||
      background: #e6e6e6;
 | 
			
		||||
    }
 | 
			
		||||
    .title {
 | 
			
		||||
      font-size: 14px;
 | 
			
		||||
      font-weight: 500;
 | 
			
		||||
      color: #a1aebe;
 | 
			
		||||
      margin: 0 19px;
 | 
			
		||||
    }
 | 
			
		||||
    .login-type {
 | 
			
		||||
      padding: 0 5px;
 | 
			
		||||
      margin-top: 25px;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      justify-content: space-between;
 | 
			
		||||
      > img {
 | 
			
		||||
        width: 30px;
 | 
			
		||||
        height: 30px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,175 @@
 | 
			
		||||
<!--
 | 
			
		||||
  * 登录
 | 
			
		||||
  * 
 | 
			
		||||
  * @Author:    1024创新实验室-主任:卓大 
 | 
			
		||||
  * @Date:      2022-09-12 22:34:00 
 | 
			
		||||
  * @Wechat:    zhuda1024 
 | 
			
		||||
  * @Email:     lab1024@163.com 
 | 
			
		||||
  * @Copyright  1024创新实验室 ( https://1024lab.net ),Since 2012 
 | 
			
		||||
  *
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="login-container">
 | 
			
		||||
    <div class="box-item desc">
 | 
			
		||||
      <div class="welcome">
 | 
			
		||||
        <p>欢迎登录 SmartAdmin V3</p>
 | 
			
		||||
        <p class="sub-welcome">高质量代码的快速开发平台</p>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="box-item login">
 | 
			
		||||
      <img class="login-qr" :src="loginQR" />
 | 
			
		||||
      <div class="login-title">账号登录</div>
 | 
			
		||||
      <a-form ref="formRef" class="login-form" :model="loginForm" :rules="rules">
 | 
			
		||||
        <a-form-item name="loginName">
 | 
			
		||||
          <a-input v-model:value.trim="loginForm.loginName" placeholder="请输入用户名" />
 | 
			
		||||
        </a-form-item>
 | 
			
		||||
        <a-form-item name="password">
 | 
			
		||||
          <a-input-password
 | 
			
		||||
            v-model:value="loginForm.password"
 | 
			
		||||
            autocomplete="on"
 | 
			
		||||
            :type="showPassword ? 'text' : 'password'"
 | 
			
		||||
            placeholder="请输入密码:至少三种字符,最小 8 位"
 | 
			
		||||
          />
 | 
			
		||||
        </a-form-item>
 | 
			
		||||
        <a-form-item name="captchaCode">
 | 
			
		||||
          <a-input class="captcha-input" v-model:value.trim="loginForm.captchaCode" placeholder="请输入验证码" />
 | 
			
		||||
          <img class="captcha-img" :src="captchaBase64Image" @click="getCaptcha" />
 | 
			
		||||
        </a-form-item>
 | 
			
		||||
        <a-form-item>
 | 
			
		||||
          <a-checkbox v-model:checked="rememberPwd">记住密码</a-checkbox>
 | 
			
		||||
          <span> ( 账号:admin, 密码:123456)</span>
 | 
			
		||||
        </a-form-item>
 | 
			
		||||
        <a-form-item>
 | 
			
		||||
          <div class="btn" @click="onLogin">登录</div>
 | 
			
		||||
        </a-form-item>
 | 
			
		||||
      </a-form>
 | 
			
		||||
      <div class="more">
 | 
			
		||||
        <div class="title-box">
 | 
			
		||||
          <p class="line"></p>
 | 
			
		||||
          <p class="title">其他方式登录</p>
 | 
			
		||||
          <p class="line"></p>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="login-type">
 | 
			
		||||
          <img src="/@/assets/images/login/wechat-icon.png" />
 | 
			
		||||
          <img src="/@/assets/images/login/ali-icon.png" />
 | 
			
		||||
          <img src="/@/assets/images/login/douyin-icon.png" />
 | 
			
		||||
          <img src="/@/assets/images/login/qq-icon.png" />
 | 
			
		||||
          <img src="/@/assets/images/login/weibo-icon.png" />
 | 
			
		||||
          <img src="/@/assets/images/login/feishu-icon.png" />
 | 
			
		||||
          <img src="/@/assets/images/login/google-icon.png" />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup>
 | 
			
		||||
  import { message } from 'ant-design-vue';
 | 
			
		||||
  import { onMounted, onUnmounted, reactive, ref } from 'vue';
 | 
			
		||||
  import { useRouter } from 'vue-router';
 | 
			
		||||
  import { loginApi } from '/@/api/system/login-api';
 | 
			
		||||
  import { SmartLoading } from '/@/components/framework/smart-loading';
 | 
			
		||||
  import { LOGIN_DEVICE_ENUM } from '/@/constants/system/login-device-const';
 | 
			
		||||
  import { useUserStore } from '/@/store/modules/system/user';
 | 
			
		||||
  import loginQR from '/@/assets/images/login/login-qr.png';
 | 
			
		||||
 | 
			
		||||
  import { buildRoutes } from '/@/router/index';
 | 
			
		||||
  import { smartSentry } from '/@/lib/smart-sentry';
 | 
			
		||||
  import { encryptData } from '/@/lib/encrypt';
 | 
			
		||||
  import { localSave } from '/@/utils/local-util.js';
 | 
			
		||||
  import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
 | 
			
		||||
 | 
			
		||||
  //--------------------- 登录表单 ---------------------------------
 | 
			
		||||
 | 
			
		||||
  const loginForm = reactive({
 | 
			
		||||
    loginName: 'admin',
 | 
			
		||||
    password: '',
 | 
			
		||||
    captchaCode: '',
 | 
			
		||||
    captchaUuid: '',
 | 
			
		||||
    loginDevice: LOGIN_DEVICE_ENUM.PC.value,
 | 
			
		||||
  });
 | 
			
		||||
  const rules = {
 | 
			
		||||
    loginName: [{ required: true, message: '用户名不能为空' }],
 | 
			
		||||
    password: [{ required: true, message: '密码不能为空' }],
 | 
			
		||||
    captchaCode: [{ required: true, message: '验证码不能为空' }],
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const showPassword = ref(false);
 | 
			
		||||
  const router = useRouter();
 | 
			
		||||
  const formRef = ref();
 | 
			
		||||
  const rememberPwd = ref(false);
 | 
			
		||||
 | 
			
		||||
  onMounted(() => {
 | 
			
		||||
    document.onkeyup = (e) => {
 | 
			
		||||
      if (e.keyCode == 13) {
 | 
			
		||||
        onLogin();
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  onUnmounted(() => {
 | 
			
		||||
    document.onkeyup = null;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  //登录
 | 
			
		||||
  async function onLogin() {
 | 
			
		||||
    formRef.value.validate().then(async () => {
 | 
			
		||||
      try {
 | 
			
		||||
        SmartLoading.show();
 | 
			
		||||
        // 密码加密
 | 
			
		||||
        let encryptPasswordForm = Object.assign({}, loginForm, {
 | 
			
		||||
          password: encryptData(loginForm.password),
 | 
			
		||||
        });
 | 
			
		||||
        const res = await loginApi.login(encryptPasswordForm);
 | 
			
		||||
        stopRefrestCaptchaInterval();
 | 
			
		||||
        localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
 | 
			
		||||
        message.success('登录成功');
 | 
			
		||||
        //更新用户信息到pinia
 | 
			
		||||
        useUserStore().setUserLoginInfo(res.data);
 | 
			
		||||
        //构建系统的路由
 | 
			
		||||
        buildRoutes();
 | 
			
		||||
        router.push('/home');
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        if (e.data && e.data.code !== 0) {
 | 
			
		||||
          loginForm.captchaCode = '';
 | 
			
		||||
          getCaptcha();
 | 
			
		||||
        }
 | 
			
		||||
        smartSentry.captureError(e);
 | 
			
		||||
      } finally {
 | 
			
		||||
        SmartLoading.hide();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  //--------------------- 验证码 ---------------------------------
 | 
			
		||||
 | 
			
		||||
  const captchaBase64Image = ref('');
 | 
			
		||||
  async function getCaptcha() {
 | 
			
		||||
    try {
 | 
			
		||||
      let captchaResult = await loginApi.getCaptcha();
 | 
			
		||||
      captchaBase64Image.value = captchaResult.data.captchaBase64Image;
 | 
			
		||||
      loginForm.captchaUuid = captchaResult.data.captchaUuid;
 | 
			
		||||
      beginRefrestCaptchaInterval(captchaResult.data.expireSeconds);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log(e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let refrestCaptchaInterval = null;
 | 
			
		||||
  function beginRefrestCaptchaInterval(expireSeconds) {
 | 
			
		||||
    if (refrestCaptchaInterval === null) {
 | 
			
		||||
      refrestCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function stopRefrestCaptchaInterval() {
 | 
			
		||||
    if (refrestCaptchaInterval != null) {
 | 
			
		||||
      clearInterval(refrestCaptchaInterval);
 | 
			
		||||
      refrestCaptchaInterval = null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onMounted(getCaptcha);
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="less" scoped>
 | 
			
		||||
  @import './login.less';
 | 
			
		||||
</style>
 | 
			
		||||
@@ -0,0 +1,204 @@
 | 
			
		||||
.login-container {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  background: url(/@/assets/images/login/login-bg.png) no-repeat center;
 | 
			
		||||
  background-size: cover;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  .box-item {
 | 
			
		||||
    width: 444px;
 | 
			
		||||
    height: 570px;
 | 
			
		||||
    &.desc {
 | 
			
		||||
      background: #ffffff;
 | 
			
		||||
      border-radius: 12px 0px 0px 12px;
 | 
			
		||||
      box-shadow: 0px 16px 73px 8px rgba(203, 203, 203, 0.2);
 | 
			
		||||
      padding: 23px 25px;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      flex-direction: column;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      justify-content: flex-start;
 | 
			
		||||
    }
 | 
			
		||||
    &.login {
 | 
			
		||||
      background: #ffffff;
 | 
			
		||||
      border-radius: 0px 12px 12px 0px;
 | 
			
		||||
      padding: 34px 42px;
 | 
			
		||||
      position: relative;
 | 
			
		||||
    }
 | 
			
		||||
    .login-qr {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      top: 0;
 | 
			
		||||
      right: 0;
 | 
			
		||||
      width: 66px;
 | 
			
		||||
      height: 66px;
 | 
			
		||||
    }
 | 
			
		||||
    .welcome {
 | 
			
		||||
      padding-top: 35px;
 | 
			
		||||
      p{
 | 
			
		||||
        color: #333333;
 | 
			
		||||
        line-height: 25px;
 | 
			
		||||
        letter-spacing: 0.26px;
 | 
			
		||||
        text-align: center;
 | 
			
		||||
        font-weight: 700;
 | 
			
		||||
        font-size: 26px;
 | 
			
		||||
      }
 | 
			
		||||
      .sub-welcome{
 | 
			
		||||
        padding: 10px;
 | 
			
		||||
        font-size: 18px;
 | 
			
		||||
        font-weight: 700;
 | 
			
		||||
        text-align: center;
 | 
			
		||||
        color: #ffffff;
 | 
			
		||||
        line-height: 25px;
 | 
			
		||||
        letter-spacing: 0.26px;
 | 
			
		||||
        opacity: 0.96;
 | 
			
		||||
        background: #1748fd;
 | 
			
		||||
        border-radius: 22px 22px 2px 22px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    .welcome-img{
 | 
			
		||||
      width: 350px;
 | 
			
		||||
      height: 350px;
 | 
			
		||||
    }
 | 
			
		||||
    .app-qr-box {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      justify-content: space-around;
 | 
			
		||||
      margin-top: 20px;
 | 
			
		||||
      .app-qr {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        justify-content: center;
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        width: 50%;
 | 
			
		||||
        > img {
 | 
			
		||||
          width: 112px;
 | 
			
		||||
          height: 112px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .qr-desc {
 | 
			
		||||
          display: flex;
 | 
			
		||||
          align-items: center;
 | 
			
		||||
          margin-top: 11px;
 | 
			
		||||
          font-size: 12px;
 | 
			
		||||
          font-weight: 500;
 | 
			
		||||
          text-align: center;
 | 
			
		||||
          color: #ffffff;
 | 
			
		||||
          > img {
 | 
			
		||||
            width: 15px;
 | 
			
		||||
            height: 18px;
 | 
			
		||||
            margin-right: 9px;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .qr-desc-marquee {
 | 
			
		||||
          width: 100%;
 | 
			
		||||
          display: flex;
 | 
			
		||||
          align-items: center;
 | 
			
		||||
          margin-top: 11px;
 | 
			
		||||
          font-size: 12px;
 | 
			
		||||
          font-weight: 500;
 | 
			
		||||
          text-align: center;
 | 
			
		||||
          color: #ffffff;
 | 
			
		||||
 | 
			
		||||
          @keyframes marquee {
 | 
			
		||||
            0% {
 | 
			
		||||
              transform: translateX(0);
 | 
			
		||||
            }
 | 
			
		||||
            100% {
 | 
			
		||||
              transform: translateX(-30%);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .marquee {
 | 
			
		||||
            flex: 1;
 | 
			
		||||
            overflow: hidden;
 | 
			
		||||
            span {
 | 
			
		||||
              display: inline-block;
 | 
			
		||||
              width: 100%;
 | 
			
		||||
              white-space: nowrap;
 | 
			
		||||
              animation: marquee 5s linear infinite;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    .login-title {
 | 
			
		||||
      font-size: 20px;
 | 
			
		||||
      text-align: center;
 | 
			
		||||
      color: #1e1e1e;
 | 
			
		||||
      margin-bottom: 35px;
 | 
			
		||||
    }
 | 
			
		||||
    .login-form {
 | 
			
		||||
      .captcha-input {
 | 
			
		||||
        width: 60%;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .captcha-img {
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    .ant-input,
 | 
			
		||||
    .ant-input-affix-wrapper {
 | 
			
		||||
      height: 44px;
 | 
			
		||||
      border: 1px solid #ededed;
 | 
			
		||||
      border-radius: 4px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .eye-box {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      right: 15px;
 | 
			
		||||
      top: 10px;
 | 
			
		||||
      .eye-icon {
 | 
			
		||||
        width: 20px;
 | 
			
		||||
        height: 20px;
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    .btn {
 | 
			
		||||
      width: 350px;
 | 
			
		||||
      height: 50px;
 | 
			
		||||
      background: #1748FD;
 | 
			
		||||
      border-radius: 4px;
 | 
			
		||||
      font-size: 16px;
 | 
			
		||||
      font-weight: 700;
 | 
			
		||||
      text-align: center;
 | 
			
		||||
      color: #ffffff;
 | 
			
		||||
      line-height: 50px;
 | 
			
		||||
      cursor: pointer;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .more {
 | 
			
		||||
    margin-top: 30px;
 | 
			
		||||
    .title-box {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      justify-content: center;
 | 
			
		||||
      > p {
 | 
			
		||||
        margin-bottom: 0;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    .line {
 | 
			
		||||
      width: 114px;
 | 
			
		||||
      height: 1px;
 | 
			
		||||
      background: #e6e6e6;
 | 
			
		||||
    }
 | 
			
		||||
    .title {
 | 
			
		||||
      font-size: 14px;
 | 
			
		||||
      font-weight: 500;
 | 
			
		||||
      color: #a1aebe;
 | 
			
		||||
      margin: 0 19px;
 | 
			
		||||
    }
 | 
			
		||||
    .login-type {
 | 
			
		||||
      padding: 0 5px;
 | 
			
		||||
      margin-top: 25px;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      justify-content: space-between;
 | 
			
		||||
      > img {
 | 
			
		||||
        width: 30px;
 | 
			
		||||
        height: 30px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,176 @@
 | 
			
		||||
<!--
 | 
			
		||||
  * 登录
 | 
			
		||||
  * 
 | 
			
		||||
  * @Author:    1024创新实验室-主任:卓大 
 | 
			
		||||
  * @Date:      2022-09-12 22:34:00 
 | 
			
		||||
  * @Wechat:    zhuda1024 
 | 
			
		||||
  * @Email:     lab1024@163.com 
 | 
			
		||||
  * @Copyright  1024创新实验室 ( https://1024lab.net ),Since 2012 
 | 
			
		||||
  *
 | 
			
		||||
-->
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="login-container">
 | 
			
		||||
    <div class="box-item desc">
 | 
			
		||||
      <div class="welcome">
 | 
			
		||||
        <p>欢迎登录 SmartAdmin V3</p>
 | 
			
		||||
        <p class="sub-welcome">高质量代码的快速开发平台</p>
 | 
			
		||||
      </div>
 | 
			
		||||
      <img class="welcome-img" src="/@/assets/images/login/left-bg2.png" />
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="box-item login">
 | 
			
		||||
      <img class="login-qr" :src="loginQR" />
 | 
			
		||||
      <div class="login-title">账号登录</div>
 | 
			
		||||
      <a-form ref="formRef" class="login-form" :model="loginForm" :rules="rules">
 | 
			
		||||
        <a-form-item name="loginName">
 | 
			
		||||
          <a-input v-model:value.trim="loginForm.loginName" placeholder="请输入用户名" />
 | 
			
		||||
        </a-form-item>
 | 
			
		||||
        <a-form-item name="password">
 | 
			
		||||
          <a-input-password
 | 
			
		||||
            v-model:value="loginForm.password"
 | 
			
		||||
            autocomplete="on"
 | 
			
		||||
            :type="showPassword ? 'text' : 'password'"
 | 
			
		||||
            placeholder="请输入密码:至少三种字符,最小 8 位"
 | 
			
		||||
          />
 | 
			
		||||
        </a-form-item>
 | 
			
		||||
        <a-form-item name="captchaCode">
 | 
			
		||||
          <a-input class="captcha-input" v-model:value.trim="loginForm.captchaCode" placeholder="请输入验证码" />
 | 
			
		||||
          <img class="captcha-img" :src="captchaBase64Image" @click="getCaptcha" />
 | 
			
		||||
        </a-form-item>
 | 
			
		||||
        <a-form-item>
 | 
			
		||||
          <a-checkbox v-model:checked="rememberPwd">记住密码</a-checkbox>
 | 
			
		||||
          <span> ( 账号:admin, 密码:123456)</span>
 | 
			
		||||
        </a-form-item>
 | 
			
		||||
        <a-form-item>
 | 
			
		||||
          <div class="btn" @click="onLogin">登录</div>
 | 
			
		||||
        </a-form-item>
 | 
			
		||||
      </a-form>
 | 
			
		||||
      <div class="more">
 | 
			
		||||
        <div class="title-box">
 | 
			
		||||
          <p class="line"></p>
 | 
			
		||||
          <p class="title">其他方式登录</p>
 | 
			
		||||
          <p class="line"></p>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="login-type">
 | 
			
		||||
          <img src="/@/assets/images/login/wechat-icon.png" />
 | 
			
		||||
          <img src="/@/assets/images/login/ali-icon.png" />
 | 
			
		||||
          <img src="/@/assets/images/login/douyin-icon.png" />
 | 
			
		||||
          <img src="/@/assets/images/login/qq-icon.png" />
 | 
			
		||||
          <img src="/@/assets/images/login/weibo-icon.png" />
 | 
			
		||||
          <img src="/@/assets/images/login/feishu-icon.png" />
 | 
			
		||||
          <img src="/@/assets/images/login/google-icon.png" />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup>
 | 
			
		||||
  import { message } from 'ant-design-vue';
 | 
			
		||||
  import { onMounted, onUnmounted, reactive, ref } from 'vue';
 | 
			
		||||
  import { useRouter } from 'vue-router';
 | 
			
		||||
  import { loginApi } from '/@/api/system/login-api';
 | 
			
		||||
  import { SmartLoading } from '/@/components/framework/smart-loading';
 | 
			
		||||
  import { LOGIN_DEVICE_ENUM } from '/@/constants/system/login-device-const';
 | 
			
		||||
  import { useUserStore } from '/@/store/modules/system/user';
 | 
			
		||||
  import loginQR from '/@/assets/images/login/login-qr.png';
 | 
			
		||||
 | 
			
		||||
  import { buildRoutes } from '/@/router/index';
 | 
			
		||||
  import { smartSentry } from '/@/lib/smart-sentry';
 | 
			
		||||
  import { encryptData } from '/@/lib/encrypt';
 | 
			
		||||
  import { localSave } from '/@/utils/local-util.js';
 | 
			
		||||
  import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
 | 
			
		||||
 | 
			
		||||
  //--------------------- 登录表单 ---------------------------------
 | 
			
		||||
 | 
			
		||||
  const loginForm = reactive({
 | 
			
		||||
    loginName: 'admin',
 | 
			
		||||
    password: '',
 | 
			
		||||
    captchaCode: '',
 | 
			
		||||
    captchaUuid: '',
 | 
			
		||||
    loginDevice: LOGIN_DEVICE_ENUM.PC.value,
 | 
			
		||||
  });
 | 
			
		||||
  const rules = {
 | 
			
		||||
    loginName: [{ required: true, message: '用户名不能为空' }],
 | 
			
		||||
    password: [{ required: true, message: '密码不能为空' }],
 | 
			
		||||
    captchaCode: [{ required: true, message: '验证码不能为空' }],
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const showPassword = ref(false);
 | 
			
		||||
  const router = useRouter();
 | 
			
		||||
  const formRef = ref();
 | 
			
		||||
  const rememberPwd = ref(false);
 | 
			
		||||
 | 
			
		||||
  onMounted(() => {
 | 
			
		||||
    document.onkeyup = (e) => {
 | 
			
		||||
      if (e.keyCode == 13) {
 | 
			
		||||
        onLogin();
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  onUnmounted(() => {
 | 
			
		||||
    document.onkeyup = null;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  //登录
 | 
			
		||||
  async function onLogin() {
 | 
			
		||||
    formRef.value.validate().then(async () => {
 | 
			
		||||
      try {
 | 
			
		||||
        SmartLoading.show();
 | 
			
		||||
        // 密码加密
 | 
			
		||||
        let encryptPasswordForm = Object.assign({}, loginForm, {
 | 
			
		||||
          password: encryptData(loginForm.password),
 | 
			
		||||
        });
 | 
			
		||||
        const res = await loginApi.login(encryptPasswordForm);
 | 
			
		||||
        stopRefrestCaptchaInterval();
 | 
			
		||||
        localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
 | 
			
		||||
        message.success('登录成功');
 | 
			
		||||
        //更新用户信息到pinia
 | 
			
		||||
        useUserStore().setUserLoginInfo(res.data);
 | 
			
		||||
        //构建系统的路由
 | 
			
		||||
        buildRoutes();
 | 
			
		||||
        router.push('/home');
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        if (e.data && e.data.code !== 0) {
 | 
			
		||||
          loginForm.captchaCode = '';
 | 
			
		||||
          getCaptcha();
 | 
			
		||||
        }
 | 
			
		||||
        smartSentry.captureError(e);
 | 
			
		||||
      } finally {
 | 
			
		||||
        SmartLoading.hide();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  //--------------------- 验证码 ---------------------------------
 | 
			
		||||
 | 
			
		||||
  const captchaBase64Image = ref('');
 | 
			
		||||
  async function getCaptcha() {
 | 
			
		||||
    try {
 | 
			
		||||
      let captchaResult = await loginApi.getCaptcha();
 | 
			
		||||
      captchaBase64Image.value = captchaResult.data.captchaBase64Image;
 | 
			
		||||
      loginForm.captchaUuid = captchaResult.data.captchaUuid;
 | 
			
		||||
      beginRefrestCaptchaInterval(captchaResult.data.expireSeconds);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.log(e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let refrestCaptchaInterval = null;
 | 
			
		||||
  function beginRefrestCaptchaInterval(expireSeconds) {
 | 
			
		||||
    if (refrestCaptchaInterval === null) {
 | 
			
		||||
      refrestCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function stopRefrestCaptchaInterval() {
 | 
			
		||||
    if (refrestCaptchaInterval != null) {
 | 
			
		||||
      clearInterval(refrestCaptchaInterval);
 | 
			
		||||
      refrestCaptchaInterval = null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onMounted(getCaptcha);
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="less" scoped>
 | 
			
		||||
  @import './login.less';
 | 
			
		||||
</style>
 | 
			
		||||