diff --git a/portal-manager-ui/admin/src/api/user.js b/portal-manager-ui/admin/src/api/user.js
index 890ab1181f140ef69a837f9d84da624164e36c2e..cc39d4616e21cab983c807ecf644d7095d3e331e 100644
--- a/portal-manager-ui/admin/src/api/user.js
+++ b/portal-manager-ui/admin/src/api/user.js
@@ -16,6 +16,10 @@ export function LogoutInterface(params) {
 export function changePassword(params) {
   return http.post(`${baseURL}/zwfw/user/change/password`, params);
 }
+// 1.2.5. 鏌ヨ闂ㄦ埛鍙e彿
+export function getSlogan(params) {
+  return http.post(`${baseURL}/base/param/key?key=${params}`);
+}
 // 绠$悊鍛樹慨鏀瑰瘑鐮�
 export function editPassword(params) {
   return http.post(`${baseURL}/zwfw/user/reset/password`, params);
@@ -24,3 +28,13 @@ export function editPassword(params) {
 export function changeForgotPassword(params) {
   return http.post(`${baseURL}/zwfw/user/forgot/password`, params);
 }
+
+// 璇佷功鏈夋晥鏈熸娴�
+export function checkCipher(params) {
+  return http.get(`${baseURL}/zwfw/cipher/check`, params);
+}
+
+// 璇佷功涓婁紶
+export function uploadCipher(params) {
+  return http.post(`${baseURL}/zwfw/cipher/upload`, params);
+}
diff --git a/portal-manager-ui/admin/src/components/licenseHint/LicenseHint.vue b/portal-manager-ui/admin/src/components/licenseHint/LicenseHint.vue
new file mode 100644
index 0000000000000000000000000000000000000000..7ac03934e54f2c5b4ee1841874643eb0ed6a52e2
--- /dev/null
+++ b/portal-manager-ui/admin/src/components/licenseHint/LicenseHint.vue
@@ -0,0 +1,238 @@
+<template>
+  <div class="license-hint" v-if="show">
+    <div class="card">
+      <div class="header">
+        <span class="icon">
+          <svg
+            fill="currentColor"
+            viewBox="0 0 20 20"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              clip-rule="evenodd"
+              d="M18 3a1 1 0 00-1.447-.894L8.763 6H5a3 3 0 000 6h.28l1.771 5.316A1 1 0 008 18h1a1 1 0 001-1v-4.382l6.553 3.276A1 1 0 0018 15V3z"
+              fill-rule="evenodd"
+            ></path>
+          </svg>
+        </span>
+        <p class="alert">璇佷功杩囨湡鎻愮ず锛�</p>
+      </div>
+
+      <p class="message">{{ licenseInfo.msg }}</p>
+
+      <div class="actions">
+        <div class="read" @click="handleUpload">
+          <div class="loading-btn" v-if="loading">
+            <span>涓婁紶涓�</span><Loading></Loading>
+          </div>
+          <span v-else>鐐瑰嚮涓婁紶鏂拌瘉涔�</span>
+        </div>
+
+        <div class="mark-as-read" :disabled="isDisabled" @click="handleClose">
+          鍏抽棴寮圭獥<span v-if="time">锛坽{ time }}锛�</span>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { uploadCipher, checkCipher } from "@/api/user";
+import { mapMutations, mapState } from "vuex";
+import Loading from "./Loading.vue";
+export default {
+  components: {
+    Loading,
+  },
+  data() {
+    return {
+      accept: "application/x-zip-compressed",
+      loading: false,
+      timer: null,
+      isDisabled: true,
+      time: 0,
+      show: true,
+    };
+  },
+  computed: {
+    ...mapState("user", ["licenseInfo"]),
+  },
+  created() {
+    this.countDown();
+  },
+  methods: {
+    ...mapMutations("user", ["set_licenseInfo"]),
+    countDown() {
+      this.time = this.duration;
+      this.timer = setInterval(() => {
+        this.time -= 1;
+        if (this.time <= 0) {
+          this.isDisabled = false;
+          this.time = 0;
+          clearInterval(this.timer);
+        }
+      }, 1000);
+    },
+    handleClose() {
+      if (this.isDisabled) return;
+      this.show = false;
+    },
+    async handleUpload() {
+      if (this.loading) return;
+      let file = await this.getFile();
+      if (!file) return;
+      this.loading = true;
+      let formData = new FormData();
+      formData.append("file", file[0]);
+      let res = await uploadCipher(formData);
+      this.loading = false;
+      if (res.code == 1) {
+        this.checkCipher();
+      }
+    },
+    // 妫€娴嬬郴缁熻瘉涔�
+    async checkCipher() {
+      let res = await checkCipher();
+      if (res.code == 1) {
+        let { startTime, endTime } = res.data;
+        let msg = `璇佷功涓婁紶鎴愬姛锛岃瘉涔︽湁鏁堟湡锛�${startTime} 鑷� ${endTime}`;
+        this.set_licenseInfo({
+          isExpire: false,
+          msg: "",
+        });
+        this.$message.success(msg);
+        this.show = false;
+      } else {
+        this.set_licenseInfo({
+          isExpire: true,
+          msg: res.msg,
+        });
+      }
+    },
+
+    // 鑾峰彇鏂囦欢
+    getFile() {
+      let input = document.createElement("input");
+      document.body.appendChild(input);
+      return new Promise((resolve, reject) => {
+        Object.assign(input, {
+          accept: this.accept,
+          onchange: () => {
+            input.files ? resolve(input.files) : reject();
+          },
+          multiple: false,
+          onerror: reject,
+          type: "file",
+          hidden: true,
+          value: null,
+        });
+        input.click();
+        document.body.removeChild(input);
+      });
+    },
+  },
+  beforeDestroy() {
+    clearInterval(this.timer);
+  },
+};
+</script>
+
+<style lang="less" scoped>
+.license-hint {
+  width: 100%;
+  height: 100%;
+  position: fixed;
+  left: 0px;
+  top: 0px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background-color: rgba(0, 0, 0, 0.5);
+  z-index: 1000;
+  .card {
+    width: 320px;
+    border-radius: 16px;
+    background-color: #fff;
+    padding: 16px;
+  }
+
+  .header {
+    display: flex;
+    align-items: center;
+    grid-gap: 16px;
+    gap: 16px;
+  }
+
+  .icon {
+    flex-shrink: 0;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 50%;
+    background-color: #0857e8;
+    padding: 8px;
+    color: rgba(255, 255, 255, 1);
+  }
+
+  .icon svg {
+    height: 16px;
+    width: 16px;
+  }
+
+  .alert {
+    font-weight: 600;
+    color: rgba(107, 114, 128, 1);
+  }
+
+  .message {
+    margin-top: 16px;
+    color: rgba(107, 114, 128, 1);
+    line-height: 24px;
+  }
+
+  .actions {
+    margin-top: 20px;
+  }
+
+  /deep/.ant-upload {
+    width: 100%;
+  }
+
+  .mark-as-read,
+  .read {
+    width: 100%;
+    height: 44px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    border: none;
+    border-radius: 8px;
+    font-size: 14px;
+    font-weight: 600;
+  }
+
+  .read {
+    background-color: #0857e8;
+    color: rgba(255, 255, 255, 1);
+  }
+  .loading-btn {
+    display: flex;
+    justify-content: center;
+    gap: 10px;
+  }
+  .mark-as-read {
+    margin-top: 8px;
+    background-color: rgba(249, 250, 251, 1);
+    color: rgba(107, 114, 128, 1);
+    transition: all 0.15s ease;
+  }
+
+  .mark-as-read:hover {
+    background-color: rgb(230, 231, 233);
+  }
+  .mark-as-read[disabled] {
+    cursor: not-allowed;
+  }
+}
+</style>
diff --git a/portal-manager-ui/admin/src/components/licenseHint/Loading.vue b/portal-manager-ui/admin/src/components/licenseHint/Loading.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b99b80ae603add1435c020b81451f21d434df679
--- /dev/null
+++ b/portal-manager-ui/admin/src/components/licenseHint/Loading.vue
@@ -0,0 +1,69 @@
+<template>
+  <div class="loader">
+    <div class="dot dot-1"></div>
+    <div class="dot dot-2"></div>
+    <div class="dot dot-3"></div>
+    <!-- <div class="dot dot-4"></div>
+    <div class="dot dot-5"></div> -->
+  </div>
+</template>
+
+<script>
+export default {};
+</script>
+
+<style lang="less" scoped>
+.loader {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  gap: 5px;
+}
+
+.dot {
+  display: inline-block;
+  width: 6px;
+  height: 6px;
+  border-radius: 50%;
+  background-color: #fff;
+  -webkit-animation: dot-pulse2 1.5s ease-in-out infinite;
+  animation: dot-pulse2 1.5s ease-in-out infinite;
+}
+
+.dot-1 {
+  animation-delay: 0s;
+}
+
+.dot-2 {
+  animation-delay: 0.3s;
+}
+
+.dot-3 {
+  animation-delay: 0.6s;
+}
+
+// .dot-4 {
+//   animation-delay: 0.9s;
+// }
+
+// .dot-5 {
+//   animation-delay: 1.2s;
+// }
+
+@keyframes dot-pulse2 {
+  0% {
+    transform: scale(0.5);
+    opacity: 0.5;
+  }
+
+  50% {
+    transform: scale(1);
+    opacity: 1;
+  }
+
+  100% {
+    transform: scale(0.5);
+    opacity: 0.5;
+  }
+}
+</style>
diff --git a/portal-manager-ui/admin/src/components/licenseHint/index.js b/portal-manager-ui/admin/src/components/licenseHint/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..fc922ed9628d421d5cff5acaf687ccd586d439c2
--- /dev/null
+++ b/portal-manager-ui/admin/src/components/licenseHint/index.js
@@ -0,0 +1,26 @@
+import vue from "vue";
+import LicenseHintModal from "./LicenseHint.vue";
+import store from "@/store"; // 瀵煎叆 Vuex store 瀹炰緥
+const licenseHintModal = vue.extend(LicenseHintModal);
+function showLicenseHintModal(duration = 5) {
+  const existingModal = document.getElementById("licenseHintModal");
+  if (!existingModal) {
+    const dom = new licenseHintModal({
+      store,
+      el: document.createElement("div"),
+      data() {
+        return {
+          duration: duration,
+        };
+      },
+    });
+    dom.$el.id = "licenseHintModal";
+    document.body.appendChild(dom.$el);
+  }
+}
+
+function registryModal() {
+  vue.prototype.$licenseHintModal = showLicenseHintModal;
+}
+
+export default registryModal;
diff --git a/portal-manager-ui/admin/src/main.js b/portal-manager-ui/admin/src/main.js
index 3a08de92b2ef012a0ee38017bbb3db586ffacfd1..20ce2922e73d329dbfc0f3a11474e7c195b669ba 100644
--- a/portal-manager-ui/admin/src/main.js
+++ b/portal-manager-ui/admin/src/main.js
@@ -61,6 +61,9 @@ Vue.prototype.$bus = new Vue();
 
 // swiper
 import "swiper/css/swiper.min.css";
+// 娉ㄥ唽璇佷功杩囨湡鎻愮ず寮圭獥
+import registryModal from "@/components/licenseHint";
+Vue.use(registryModal);
 
 Vue.config.productionTip = false;
 // 鍥剧墖棰勮
diff --git a/portal-manager-ui/admin/src/router/index.js b/portal-manager-ui/admin/src/router/index.js
index d362a67764f3acd07fa6d4afb11be85295d1abb0..5f4803abaef590927103a72de74bf026558ff559 100644
--- a/portal-manager-ui/admin/src/router/index.js
+++ b/portal-manager-ui/admin/src/router/index.js
@@ -27,6 +27,10 @@ router.beforeEach((to, from, next) => {
   // let bol = hasIntersection(toRootPathArr, routerPath);
   if (islogin) {
     next();
+    let licenseInfo = store.getters["user/licenseInfo"];
+    if (licenseInfo.isExpire) {
+      Vue.prototype.$licenseHintModal();
+    }
     // if (routerPath.includes(to.path) || bol) {
     //   next();
     // }
diff --git a/portal-manager-ui/admin/src/store/modules/user.js b/portal-manager-ui/admin/src/store/modules/user.js
index a0b786d1653e23a4858ed9eeecb9eaaabac7a55f..6217484f65a91afb7cb808578af082e1f2d9ffb1 100644
--- a/portal-manager-ui/admin/src/store/modules/user.js
+++ b/portal-manager-ui/admin/src/store/modules/user.js
@@ -12,6 +12,11 @@ export default {
     siteId: "", // 绔欑偣id
     searForm: {}, // 鎶ヨ〃鎼滅储
     routerList: [], // 鐢ㄦ埛鏉冮檺璺敱
+    licenseInfo: {
+      // 绯荤粺璇佷功淇℃伅
+      isExpire: false, // 鏄惁杩囨湡,
+      msg: "", // 杩囨湡鎻愮ず淇℃伅
+    },
   },
   getters: {
     siteId: (state) => state.siteId,
@@ -29,6 +34,9 @@ export default {
     siteTreeList(state) {
       return state.siteList;
     },
+    licenseInfo(state) {
+      return state.licenseInfo;
+    },
   },
   mutations: {
     SET_routerList(state, routerList) {
@@ -58,6 +66,10 @@ export default {
     set_siteId(state, siteId) {
       state.siteId = siteId;
     },
+    set_licenseInfo(state, { isExpire, msg }) {
+      state.licenseInfo.isExpire = isExpire;
+      state.licenseInfo.msg = msg;
+    },
     // 閲嶇疆鎵€鏈変粨搴撶姸鎬�
     reset: () => {},
   },
diff --git a/portal-manager-ui/admin/src/views/signIn/signIn.vue b/portal-manager-ui/admin/src/views/signIn/signIn.vue
index 7cf729f7600f09d6be0dcda85d69d07359c0afda..fbea0ebac67434dec73792ddf03c8c2ab1395710 100644
--- a/portal-manager-ui/admin/src/views/signIn/signIn.vue
+++ b/portal-manager-ui/admin/src/views/signIn/signIn.vue
@@ -245,7 +245,11 @@
 
 <script>
 import Swiper from "swiper";
-import { LoginInterface, changeForgotPassword } from "@/api/user.js";
+import {
+  LoginInterface,
+  changeForgotPassword,
+  checkCipher,
+} from "@/api/user.js";
 import { mapMutations, mapState } from "vuex";
 import { changeAccount, changePassWord } from "@/utils/js/validate";
 import { encrypt, findSitesById } from "@/utils";
@@ -323,7 +327,9 @@ export default {
     this.createCode();
   },
   mounted() {
+    this.checkCipher();
     this.initSwiper();
+    this.$licenseHintModal();
   },
   methods: {
     ...mapMutations("user", [
@@ -331,7 +337,24 @@ export default {
       "SET_USERDATA",
       "set_siteList",
       "SET_routerList",
+      "set_licenseInfo",
     ]),
+    // 妫€娴嬬郴缁熻瘉涔︽槸鍚﹁繃鏈�
+    async checkCipher() {
+      let res = await checkCipher();
+      if (res.code == 1) {
+        this.set_licenseInfo({
+          isExpire: false,
+          msg: "",
+        });
+      } else {
+        this.set_licenseInfo({
+          isExpire: true,
+          msg: res.msg,
+        });
+        this.$licenseHintModal();
+      }
+    },
     initSwiper() {
       this.mySwiper = new Swiper(".mySwiper", {
         effect: "cube", // 鏂瑰潡鍔ㄧ敾