Browse Source

合并V3.0.1

Yue 2 years ago
parent
commit
4bde7d2340

+ 21 - 32
src/App.vue

@@ -4,46 +4,35 @@
   </el-config-provider>
 </template>
 
-<script lang="ts">
-import { defineComponent, nextTick, onBeforeMount, onMounted } from "vue"
+<script lang="ts" setup>
+import { nextTick, onBeforeMount, onMounted } from "vue"
 import { RouterView } from "vue-router"
 import appStore from "@/stores"
 import { themeMode } from "@/core/helpers/config"
 import { initializeComponents } from "@/core/plugins/keenthemes"
 import zhCn from "element-plus/lib/locale/lang/zh-cn"
 
-export default defineComponent({
-  name: "app",
-  components: {
-    RouterView,
-  },
-  setup() {
-    const configBodyStore = appStore.bodyConfigStore
-    const locale = zhCn
-    onBeforeMount(() => {
-      /**
-       * Overrides the layout config using saved data from localStorage
-       * remove this to use static config (@/core/config/DefaultLayoutConfig.ts)
-       */
-      configBodyStore.overrideLayoutConfig()
-
-      /**
-       *  Sets a mode from configuration
-       */
-      configBodyStore.setThemeMode(themeMode.value)
-    })
+const configBodyStore = appStore.bodyConfigStore
+const locale = zhCn
+onBeforeMount(() => {
+  /**
+   * Overrides the layout config using saved data from localStorage
+   * remove this to use static config (@/core/config/DefaultLayoutConfig.ts)
+   */
+  configBodyStore.overrideLayoutConfig()
+
+  /**
+   *  Sets a mode from configuration
+   */
+  configBodyStore.setThemeMode(themeMode.value)
+})
 
-    onMounted(() => {
-      nextTick(() => {
-        initializeComponents()
+onMounted(() => {
+  nextTick(() => {
+    initializeComponents()
 
-        configBodyStore.removeBodyClassName("page-loading")
-      })
-    })
-    return {
-      locale,
-    }
-  },
+    configBodyStore.removeBodyClassName("page-loading")
+  })
 })
 </script>
 

+ 1 - 0
src/components/Forms/VbFormItem.vue

@@ -102,6 +102,7 @@ onMounted(() => {
           :class="item.class"
           :style="item.style"
           :size="item.size"
+          :placeholder="item.placeholder ?? item.label"
           v-bind="item.props"
           v-on="item.listeners"
         >

+ 1 - 0
src/core/config/Index.ts

@@ -1,6 +1,7 @@
 const configs = {
   BASE_URL: import.meta.env.VITE_APP_API_URL,
   DATE_FORMAT_STRING: "YYYY年 MM月 DD日",
+  AUTH_EXPIRE_MINUTES: 60, //登录时效
   MAIN_ROUTER_NAME: "dashboard",
   SIGN_ROUTE_NAME: "sign-in",
 }

+ 18 - 3
src/core/services/JwtService.ts

@@ -1,12 +1,27 @@
 import storage from "@/core/utils/storages"
+import configs from "../config/Index"
 const ID_TOKEN_KEY = "id_token" as string
 const ID_TENANT_KEY = "id_tenant" as string
 
 /**
  * @description 获取令牌
  */
-export const getToken = (): string | null => {
-  return storage.getItem(ID_TOKEN_KEY)
+export const getToken = (isCheck = true): string | null | undefined => {
+  const arr = storage.getItem(ID_TOKEN_KEY)?.split("@@")
+  let token
+  if (arr && arr.length == 2) {
+    if (isCheck) {
+      const diff = new Date().getTime() - Number(arr[0])
+      const expire = configs.AUTH_EXPIRE_MINUTES * 60 * 1000
+      token = arr[1]
+      if (diff > expire) {
+        console.error("AUTH过期,请重新登录")
+        return null
+      }
+      saveToken(token)
+    }
+  }
+  return token
 }
 
 /**
@@ -14,7 +29,7 @@ export const getToken = (): string | null => {
  * @param token: string
  */
 export const saveToken = (token: string): void => {
-  storage.setItem(ID_TOKEN_KEY, token)
+  storage.setItem(ID_TOKEN_KEY, `${new Date().getTime()}@@${token}`)
 }
 
 /**

+ 1 - 1
src/core/services/RequestService.ts

@@ -5,7 +5,7 @@ import VueAxios from "vue-axios"
 import JwtService from "@/core/services/JwtService"
 import configs from "@/core/config/Index"
 import { dialog, toast } from "@/core/utils/message"
-import router from "@/router/Index"
+import router from "@/router"
 
 export interface errorObj {
   message: string

+ 0 - 10
src/layouts/main-layout/header/Navbar.vue

@@ -26,16 +26,6 @@
     </div>
     <!--begin::Notifications-->
     <div class="app-navbar-item ms-1 ms-lg-3">
-      <div
-        class="btn btn-icon btn-custom btn-icon-muted btn-active-light btn-active-color-primary w-30px h-30px w-md-40px h-md-40px"
-        data-kt-menu-trigger="click"
-        data-kt-menu-attach="parent"
-        data-kt-menu-placement="bottom-end"
-      >
-        <span class="svg-icon svg-icon-1">
-          <inline-svg :src="getAssetPath('media/icons/duotune/general/gen022.svg')" />
-        </span>
-      </div>
       <KTNotificationMenu />
     </div>
     <!--end::Notifications-->

+ 164 - 323
src/layouts/main-layout/menus/NotificationsMenu.vue

@@ -1,335 +1,176 @@
+<script lang="ts" setup>
+import { ref, onMounted, computed, watch } from "vue"
+import { getAssetPath } from "@/core/helpers/assets"
+import Rs from "@/core/services/RequestService"
+import appStore from "@/stores"
+import router from "@/router"
+const total = ref(0)
+const curPage = ref(0)
+const perPage = ref(10)
+const params = computed(() => {
+  return {
+    pageIndex: curPage.value,
+    pageSize: perPage.value,
+    params: { readFlag: false },
+  }
+})
+let reLoadTimre: any
+const listBox = ref<HTMLElement>()
+const dataList = ref<Array<any>>([])
+function jump(item: any) {
+  read(item.id)
+  switch (appStore.authStore.user?.userType) {
+    case "2": //企业账号
+      if (item.eventType == "000100003" || item.eventType == "000100005" || item.eventType == "000100007") {
+        router.push({
+          path: "/Abnormal/AbnormalList/strongWarn",
+          query: {
+            id: item.warn_id,
+            type: item.eventType,
+          },
+        })
+      } else if (item.eventType == "000100006") {
+        router.push({
+          path: "/Abnormal/AbnormalList/disConnect",
+          query: {
+            id: item.warn_id,
+            type: item.eventType,
+          },
+        })
+      } else {
+        router.push({
+          path: "/Abnormal/AbnormalList/overdueWarn",
+          query: {
+            id: item.warn_id,
+            type: item.eventType,
+          },
+        })
+      }
+      break
+    case "0": //系统管理员
+    case "1": //政府账号
+    default:
+      if (item.eventType == "000100003" || item.eventType == "000100005" || item.eventType == "000100007") {
+        router.push({
+          path: "/warn/warnList/concentrationWarn",
+          query: {
+            id: item.warn_id,
+            type: item.eventType,
+          },
+        })
+      } else if (item.eventType == "000100006") {
+        router.push({
+          path: "/warn/warnList/offlineWarn",
+          query: {
+            id: item.warn_id,
+            type: item.eventType,
+          },
+        })
+      } else {
+        router.push({
+          path: "/warn/warnList/overdueWarn",
+          query: {
+            id: item.warn_id,
+            type: item.eventType,
+          },
+        })
+      }
+      break
+  }
+}
+function read(id: string) {
+  Rs.get(`sys/pushEvent/read?noticeId=${id}`).then(() => {
+    clearTimeout(reLoadTimre)
+    reLoad()
+  })
+}
+function readAll() {
+  Rs.get(`sys/pushEvent/clear`).then(() => {
+    clearTimeout(reLoadTimre)
+    reLoad()
+  })
+}
+function load() {
+  curPage.value++
+  Rs.post("sys/pushEvent/getList", { data: params.value, successAlert: false }).then((res: any) => {
+    console.log("NOTIFI", res.data)
+    dataList.value.push(...res.data)
+    total.value = res.total
+  })
+}
+function reLoad() {
+  curPage.value = 0
+  load()
+  dataList.value = []
+  reLoadTimre = setTimeout(reLoad, 1000 * 60 * 3)
+}
+const onScroll = () => {
+  if (listBox.value) {
+    const diff = listBox.value.scrollHeight - (listBox.value.scrollTop + listBox.value.clientHeight)
+    //console.log("TOP", diff, listBox.value?.scrollTop, listBox.value?.scrollHeight, listBox.value?.clientHeight)
+    if (diff <= 0) {
+      load()
+    }
+  }
+}
+watch(listBox, () => {
+  if (listBox.value) {
+    listBox.value.removeEventListener("scroll", onScroll, true)
+    listBox.value.addEventListener("scroll", onScroll, true)
+  }
+})
+onMounted(() => {
+  reLoad()
+})
+</script>
+
 <template>
-  <!--begin::Menu-->
-  <div class="menu menu-sub menu-sub-dropdown menu-column w-350px w-lg-375px" data-kt-menu="true">
-    <!--begin::Heading-->
+  <div
+    class="btn btn-icon btn-custom btn-icon-muted btn-active-light btn-active-color-primary w-30px h-30px w-md-40px h-md-40px position-relative"
+    data-kt-menu-trigger="click"
+    data-kt-menu-attach="parent"
+    data-kt-menu-placement="bottom-end"
+  >
+    <span class="svg-icon svg-icon-1">
+      <inline-svg :src="getAssetPath('media/icons/duotune/general/gen022.svg')" />
+    </span>
+    <span
+      v-if="total > 0"
+      class="bullet bullet-dot bg-warning h-6px w-6px position-absolute translate-middle top-0 start-50 animation-blink"
+    ></span>
+  </div>
+  <div class="menu menu-sub menu-sub-dropdown menu-column w-350px w-lg-400px" data-kt-menu="true">
     <div
       class="d-flex flex-column bgi-no-repeat rounded-top"
       :style="`background-image: url('${getAssetPath('/media/misc/menu-header-bg.jpg')}')`"
     >
-      <!--begin::Title-->
-      <h3 class="text-white fw-semobold px-9 mt-10 mb-6">
-        Notifications
-        <span class="fs-8 opacity-75 ps-3">24 reports</span>
+      <h3 class="text-white fw-semobold px-9 mt-10 mb-6 d-flex align-items-center justify-content-between">
+        <span>
+          消息通知
+          <span class="fs-8 opacity-75 ps-3" v-if="total > 0">{{ total }} 条</span>
+        </span>
+        <span class="svg-icon svg-icon-1 btn p-1" @click="readAll" v-tooltip title="清空通知">
+          <inline-svg :src="getAssetPath('media/icons/duotune/general/gen064.svg')" />
+        </span>
       </h3>
-      <!--end::Title-->
-
-      <!--begin::Tabs-->
-      <ul class="nav nav-line-tabs nav-line-tabs-2x nav-stretch fw-semobold px-9">
-        <li class="nav-item">
-          <a
-            class="nav-link text-white opacity-75 opacity-state-100 pb-4"
-            data-bs-toggle="tab"
-            href="#kt_topbar_notifications_1"
-          >
-            Alerts
-          </a>
-        </li>
-
-        <li class="nav-item">
-          <a
-            class="nav-link text-white opacity-75 opacity-state-100 pb-4 active"
-            data-bs-toggle="tab"
-            href="#kt_topbar_notifications_2"
-          >
-            Updates
-          </a>
-        </li>
-
-        <li class="nav-item">
-          <a
-            class="nav-link text-white opacity-75 opacity-state-100 pb-4"
-            data-bs-toggle="tab"
-            href="#kt_topbar_notifications_3"
-          >
-            Logs
-          </a>
-        </li>
-      </ul>
-      <!--end::Tabs-->
     </div>
-    <!--end::Heading-->
-
-    <!--begin::Tab content-->
-    <div class="tab-content">
-      <!--begin::Tab panel-->
-      <div class="tab-pane fade" id="kt_topbar_notifications_1" role="tabpanel">
-        <!--begin::Items-->
-        <div class="scroll-y mh-325px my-5 px-8">
-          <template v-for="(item, index) in data1" :key="index">
-            <!--begin::Item-->
-            <div class="d-flex flex-stack py-4">
-              <!--begin::Section-->
-              <div class="d-flex align-items-center">
-                <!--begin::Symbol-->
-                <div class="symbol symbol-35px me-4">
-                  <span :class="`bg-light-${item.state}`" class="symbol-label">
-                    <span :class="`svg-icon-${item.state}`" class="svg-icon svg-icon-2">
-                      <inline-svg :src="item.icon" />
-                    </span>
-                  </span>
-                </div>
-                <!--end::Symbol-->
-
-                <!--begin::Title-->
-                <div class="mb-0 me-2">
-                  <a href="#" class="fs-6 text-gray-800 text-hover-primary fw-bold">{{ item.title }}</a>
-                  <div class="text-gray-400 fs-7">
-                    {{ item.description }}
-                  </div>
-                </div>
-                <!--end::Title-->
-              </div>
-              <!--end::Section-->
-
-              <!--begin::Label-->
-              <span class="badge badge-light fs-8">{{ item.time }}</span>
-              <!--end::Label-->
-            </div>
-            <!--end::Item-->
-          </template>
-        </div>
-        <!--end::Items-->
-
-        <!--begin::View more-->
-        <div class="py-3 text-center border-top">
-          <a href="#" class="btn btn-color-gray-600 btn-active-color-primary">
-            View All
-            <span class="svg-icon svg-icon-5">
-              <inline-svg :src="getAssetPath('media/icons/duotune/arrows/arr064.svg')" />
-            </span>
-          </a>
-        </div>
-        <!--end::View more-->
-      </div>
-      <!--end::Tab panel-->
-
-      <!--begin::Tab panel-->
-      <div class="tab-pane fade show active" id="kt_topbar_notifications_2" role="tabpanel">
-        <!--begin::Wrapper-->
-        <div class="d-flex flex-column px-9">
-          <!--begin::Section-->
-          <div class="pt-10 pb-0">
-            <!--begin::Title-->
-            <h3 class="text-dark text-center fw-bold">Get Pro Access</h3>
-            <!--end::Title-->
-
-            <!--begin::Text-->
-            <div class="text-center text-gray-600 fw-semobold pt-1">
-              Outlines keep you honest. They stoping you from amazing poorly about drive
-            </div>
-            <!--end::Text-->
-
-            <!--begin::Action-->
-            <div class="text-center mt-5 mb-9">
-              <a
-                href="#"
-                class="btn btn-sm btn-primary px-6"
-                data-bs-toggle="modal"
-                data-bs-target="#kt_modal_upgrade_plan"
-              >
-                Upgrade
-              </a>
-            </div>
-            <!--end::Action-->
+    <div ref="listBox" v-if="dataList.length" class="h-500px overflow-auto p-2">
+      <template v-for="(v, i) in dataList" :key="i">
+        <div class="d-flex px-3 align-items-center" @click="jump(v)" style="cursor: pointer">
+          <span class="svg-icon svg-icon-1 svg-icon-warning ps-2 pe-5">
+            <inline-svg :src="getAssetPath('media/icons/duotune/abstract/abs038.svg')" />
+          </span>
+          <div class="d-flex flex-column">
+            <span class="text-gray-700">{{ v.content }}</span>
+            <span class="text-gray-600">{{ v.time }}</span>
           </div>
-          <!--end::Section-->
-
-          <!--begin::Illustration-->
-          <img class="mw-100 mh-200px" alt="metronic" :src="getIllustrationsPath('1.png')" />
-          <!--end::Illustration-->
-        </div>
-        <!--end::Wrapper-->
-      </div>
-      <!--end::Tab panel-->
-
-      <!--begin::Tab panel-->
-      <div class="tab-pane fade" id="kt_topbar_notifications_3" role="tabpanel">
-        <!--begin::Items-->
-        <div class="scroll-y mh-325px my-5 px-8">
-          <template v-for="(item, index) in data2" :key="index">
-            <!--begin::Item-->
-            <div class="d-flex flex-stack py-4">
-              <!--begin::Section-->
-              <div class="d-flex align-items-center me-2">
-                <!--begin::Code-->
-                <span class="w-70px badge me-4" :class="`badge-light-${item.state}`">{{ item.code }}</span>
-                <!--end::Code-->
-
-                <!--begin::Title-->
-                <a href="#" class="text-gray-800 text-hover-primary fw-semobold">{{ item.message }}</a>
-                <!--end::Title-->
-              </div>
-              <!--end::Section-->
-
-              <!--begin::Label-->
-              <span class="badge badge-light fs-8">{{ item.time }}</span>
-              <!--end::Label-->
-            </div>
-            <!--end::Item-->
-          </template>
-        </div>
-        <!--end::Items-->
-
-        <!--begin::View more-->
-        <div class="py-3 text-center border-top">
-          <a href="#" class="btn btn-color-gray-600 btn-active-color-primary">
-            View All
-            <span class="svg-icon-svg-icon-5">
-              <inline-svg :src="getAssetPath('media/icons/duotune/arrows/arr064.svg')" />
-            </span>
-          </a>
         </div>
-        <!--end::View more-->
-      </div>
-      <!--end::Tab panel-->
+        <div class="separator my-2"></div>
+      </template>
+    </div>
+    <div v-else class="h-150px d-flex justify-content-center flex-column align-items-center">
+      <img class="mb-2" :src="getAssetPath('media/table/empty.svg')" />
+      <span class="text-gray-500">暂无消息通知</span>
     </div>
-    <!--end::Tab content-->
   </div>
-  <!--end::Menu-->
 </template>
-
-<script lang="ts">
-import { getAssetPath } from "@/core/helpers/assets"
-import { defineComponent } from "vue"
-import { getIllustrationsPath } from "@/core/helpers/assets"
-
-export default defineComponent({
-  name: "notifications-menu",
-  components: {},
-  setup() {
-    const data1 = [
-      {
-        title: "Project Alice",
-        description: "Phase 1 development",
-        time: "1 hr",
-        icon: getAssetPath("media/icons/duotune/technology/teh008.svg"),
-        state: "primary",
-      },
-      {
-        title: "HR Confidential",
-        description: "Confidential staff documents",
-        time: "2 hrs",
-        icon: getAssetPath("media/icons/duotune/general/gen044.svg"),
-        state: "danger",
-      },
-      {
-        title: "Company HR",
-        description: "Corporeate staff profiles",
-        time: "5 hrs",
-        icon: getAssetPath("media/icons/duotune/finance/fin006.svg"),
-        state: "warning",
-      },
-      {
-        title: "Project Redux",
-        description: "New frontend admin theme",
-        time: "2 days",
-        icon: getAssetPath("media/icons/duotune/files/fil023.svg"),
-        state: "success",
-      },
-      {
-        title: "Project Breafing",
-        description: "Product launch status update",
-        time: "21 Jan",
-        icon: getAssetPath("media/icons/duotune/maps/map001.svg"),
-        state: "primary",
-      },
-      {
-        title: "Banner Assets",
-        description: "Collection of banner images",
-        time: "21 Jan",
-        icon: getAssetPath("media/icons/duotune/general/gen006.svg"),
-        state: "info",
-      },
-      {
-        title: "Icon Assets",
-        description: "Collection of SVG icons",
-        time: "20 March",
-        icon: getAssetPath("media/icons/duotune/art/art002.svg"),
-        state: "warning",
-      },
-    ]
-
-    const data2 = [
-      {
-        code: "200 OK",
-        state: "success",
-        message: "New order",
-        time: "Just now",
-      },
-      {
-        code: "500 ERR",
-        state: "danger",
-        message: "New customer",
-        time: "2 hrs",
-      },
-      {
-        code: "200 OK",
-        state: "success",
-        message: "Payment process",
-        time: "5 hrs",
-      },
-      {
-        code: "300 WRN",
-        state: "warning",
-        message: "Search query",
-        time: "2 days",
-      },
-      {
-        code: "200 OK",
-        state: "success",
-        message: "API connection",
-        time: "1 week",
-      },
-      {
-        code: "200 OK",
-        state: "success",
-        message: "Database restore",
-        time: "Mar 5",
-      },
-      {
-        code: "300 WRN",
-        state: "warning",
-        message: "System update",
-        time: "May 15",
-      },
-      {
-        code: "300 WRN",
-        state: "warning",
-        message: "Server OS update",
-        time: "Apr 3",
-      },
-      {
-        code: "300 WRN",
-        state: "warning",
-        message: "API rollback",
-        time: "Jun 30",
-      },
-      {
-        code: "500 ERR",
-        state: "danger",
-        message: "Refund process",
-        time: "Jul 10",
-      },
-      {
-        code: "500 ERR",
-        state: "danger",
-        message: "Withdrawal process",
-        time: "Sep 10",
-      },
-      {
-        code: "500 ERR",
-        state: "danger",
-        message: "Mail tasks",
-        time: "Dec 10",
-      },
-    ]
-
-    return {
-      data1,
-      data2,
-      getIllustrationsPath,
-      getAssetPath,
-    }
-  },
-})
-</script>

+ 89 - 3
src/layouts/main-layout/menus/UserAccountMenu.vue

@@ -17,7 +17,7 @@
         <div class="d-flex flex-column">
           <div class="fw-bold d-flex align-items-center fs-5">
             {{ store.user?.userName }}
-            <span class="badge badge-light-success fw-bold fs-8 px-2 py-1 ms-2">Pro</span>
+            <span class="badge badge-light-success fw-bold fs-8 px-2 py-1 ms-2">{{ store.user?.orgId }}</span>
           </div>
         </div>
         <!--end::Username-->
@@ -37,23 +37,109 @@
 
     <!--begin::Menu item-->
     <div class="menu-item px-5">
+      <a @click="password()" class="menu-link px-5">修改密码</a>
       <a @click="signOut()" class="menu-link px-5">注销</a>
     </div>
     <!--end::Menu item-->
   </div>
+  <Teleport to="body">
+    <VbModal
+      id="change-password"
+      modal-style="z-index:10000"
+      title="修改密码"
+      v-model:modal="pwdModal"
+      v-model:form-data="formData"
+      :form-items="items"
+      @confirm="changePassword"
+    ></VbModal>
+  </Teleport>
+
   <!--end::Menu-->
 </template>
 
 <script lang="ts" setup>
+import { ref } from "vue"
 import { getAssetPath } from "@/core/helpers/assets"
 import appStore from "@/stores"
 import { useRouter } from "vue-router"
 import configs from "@/core/config/Index"
+import type { VbFormItem } from "@/components/Forms/models"
+import Rs from "@/core/services/RequestService"
+import { ElInput } from "element-plus"
+import { dialog } from "@/core/utils/message"
 const router = useRouter()
 const store = appStore.authStore
 
 const signOut = () => {
-  store.logout()
-  router.push({ name: configs.SIGN_ROUTE_NAME })
+  dialog.confirm("确定要注销登录吗?", "注销", (confirm: any) => {
+    if (confirm.isConfirmed) {
+      store.logout()
+      router.push({ name: configs.SIGN_ROUTE_NAME })
+    }
+  })
+}
+const pwdModal = ref()
+const formData = ref({
+  oldPassword: "",
+  cPassword: "",
+  password: "",
+})
+const validatePass = (rule: any, value: any, callback: any) => {
+  if (
+    formData.value.password !== "" &&
+    formData.value.cPassword !== "" &&
+    formData.value.cPassword != formData.value.password
+  ) {
+    callback(new Error("密码不一致,请重新输入"))
+  }
+  callback()
+}
+const items: Array<VbFormItem> = [
+  {
+    field: "oldPassword",
+    label: "原密码",
+    required: true,
+    component: ElInput,
+    type: "password",
+    class: "w-100",
+    placeholder: "请输入原密码",
+  },
+  {
+    field: "password",
+    label: "新密码",
+    required: true,
+    component: ElInput,
+    type: "password",
+    class: "w-100",
+    placeholder: "请输入新密码",
+    rules: [{ validator: validatePass, trigger: "blur" }],
+  },
+  {
+    field: "cPassword",
+    label: "确认密码",
+    required: true,
+    component: ElInput,
+    type: "password",
+    class: "w-100",
+    placeholder: "请输入确认密码",
+    rules: [{ validator: validatePass, trigger: "blur" }],
+  },
+]
+function password() {
+  formData.value = {
+    oldPassword: "",
+    cPassword: "",
+    password: "",
+  }
+  pwdModal.value.show()
+}
+function changePassword() {
+  Rs.post("auth/users/changePassWord", {
+    data: {
+      oldPassword: formData.value.oldPassword,
+      userName: store.user?.userName,
+      password: formData.value.password,
+    },
+  })
 }
 </script>

+ 2 - 0
src/stores/_auth.ts

@@ -7,6 +7,7 @@ import storages from "@/core/utils/storages"
 export interface User {
   userName: string
   userType: string
+  orgId: string
   token: string
 }
 
@@ -45,6 +46,7 @@ export const useAuthStore = defineStore("auth", () => {
           setAuth({
             userName: result.authName || "",
             userType: result.userType || "0",
+            orgId: result.enterpriseId || "",
             token: result.auth,
           })
           resolve(result.loginMsg)

+ 262 - 0
src/views/analysisInfo/_OverDate.vue

@@ -0,0 +1,262 @@
+<script setup lang="ts">
+import { ref, onMounted } from "vue"
+import moment from "moment"
+import Rs from "@/core/services/RequestService"
+import { useRouter } from "vue-router"
+const router = useRouter()
+const ringOptions = {
+  legend: { top: "center", bottom: "auto", right: "20%", orient: "vertical" },
+  pieCenter: ["40%", "50%"],
+  titleText: "",
+}
+const timeType = ref(0)
+const date = ref<[Date, Date]>([new Date(), new Date()])
+const chartDatas = ref<Array<any>>([{}, {}, {}, {}])
+function queryChart(type: number) {
+  let url = ""
+  let data: any = {}
+  switch (type) {
+    case 0:
+      url = "sys/overdueCompany/selectOverdueCountByOrgChart"
+      data = {
+        time_type: timeType.value,
+        query_start_time: moment(date.value[0]).format("YYYYMMDD"),
+        query_end_time: moment(date.value[1]).format("YYYYMMDD"),
+      }
+      break
+    case 1:
+      url = "sys/overdueCompany/selectPurifierCountByOrgChart"
+      break
+  }
+  if (type == 1) {
+    Rs.get(url).then((res) => {
+      chartDatas.value[type] = res.data
+      chartDatas.value[type].chartData.title = ""
+    })
+  } else {
+    Rs.post(url, { data: data, successAlert: false }).then((res) => {
+      chartDatas.value[type] = res.data
+      chartDatas.value[type].chartData.title = ""
+    })
+  }
+}
+
+const size = ref<any>("default")
+const dySearchSelectStyle = { width: "120px" }
+const companyName = ref("")
+const orgId = ref<string | null>(null)
+//const operateStatus = ref("")
+const type = ref("")
+const cateringScale = ref("")
+const cateringStyle = ref("")
+const queryParams = ref<any>({
+  time_type: timeType.value,
+})
+const cols = ref([
+  {
+    name: "序号",
+    field: "index",
+  },
+  {
+    name: "区域",
+    field: "org_name",
+  },
+  {
+    name: "商户名称",
+    field: "name",
+  },
+  {
+    name: "菜系",
+    field: "catering_style_name",
+  },
+  {
+    name: "规模",
+    field: "catering_scale_name",
+  },
+  {
+    name: "净化器数量",
+    field: "purifier_num",
+  },
+  {
+    name: "操作",
+    width: 150,
+    field: "action",
+  },
+])
+const jump = function (v: any) {
+  console.log("jump", v)
+  router.push({
+    path: "/goLineData/oilFumeConcentration",
+    query: {
+      comName: v.company_name,
+      company_id: v.company_id,
+      backNeed: 1,
+    },
+  })
+}
+
+function query() {
+  const params = {
+    time_type: timeType.value,
+    org_id: orgId.value,
+    name: companyName.value,
+    //operate_status: operateStatus.value,
+    type: type.value,
+    catering_scale: cateringScale.value,
+    catering_style: cateringStyle.value,
+    query_start_time: moment(date.value[0]).format("YYYYMMDD"),
+    query_end_time: moment(date.value[1]).format("YYYYMMDD"),
+  }
+  // const keys = Object.keys(params)
+  // keys.forEach((key) => {
+  //   if (params[key] == "" && key != "clean_flag" && key != "exceed_company_flag") {
+  //     delete params[key]
+  //   }
+  // })
+  queryParams.value = params
+  queryChart(0)
+  queryChart(1)
+}
+function reset() {
+  type.value = ""
+  companyName.value = ""
+  orgId.value = "0"
+  //operateStatus.value = ""
+  type.value = ""
+  cateringScale.value = ""
+  cateringStyle.value = ""
+  timeType.value = 0
+  date.value = [new Date(), new Date()]
+  query()
+}
+function exported() {
+  const params = {
+    name: companyName.value,
+    catering_scale: cateringScale.value,
+    catering_style: cateringStyle.value,
+    type: type.value,
+    org_id: orgId.value,
+    time_type: timeType.value,
+    query_start_time: moment(date.value[0]).format("YYYYMMDD"),
+    query_end_time: moment(date.value[1]).format("YYYYMMDD"),
+  }
+  let str = ""
+  for (const key in params) {
+    str += `${key}=${params[key]}&`
+  }
+  window.open("/api/sys/overdueCompany/export?" + str)
+}
+const detail = function (row: any) {
+  router.push({
+    path: "/purify/washInfo/details",
+    query: { company_id: row.company_id },
+  })
+}
+function init() {
+  queryChart(0)
+  queryChart(1)
+}
+onMounted(init)
+</script>
+
+<template>
+  <el-row :gutter="20" class="mb-8">
+    <el-col :span="12">
+      <div class="card card-bordered border-primary h-325px">
+        <div class="card-header bg-light-primary min-h-50px">
+          <div class="card-title">
+            <h3 class="text-primary">逾期商户数量</h3>
+          </div>
+        </div>
+        <div class="card-body p-2">
+          <BaseChart :data="chartDatas[0]" type="ring" h="100%" :options="ringOptions"></BaseChart>
+        </div>
+      </div>
+    </el-col>
+    <el-col :span="12">
+      <div class="card card-bordered border-primary h-325px">
+        <div class="card-header bg-light-primary min-h-50px">
+          <div class="card-title">
+            <h3 class="text-primary">净化器数量</h3>
+          </div>
+        </div>
+        <div class="card-body p-2">
+          <BaseChart :data="chartDatas[1]" type="ring" h="100%" :options="ringOptions"></BaseChart>
+        </div>
+      </div>
+    </el-col>
+  </el-row>
+  <VbDataTable
+    ref="table"
+    :header="cols"
+    url="sys/overdueCompany/selectForPage"
+    method="post"
+    :query-params="queryParams"
+    :has-checkbox="false"
+  >
+    <template v-slot:table-tool="">
+      <el-form class="align-items-center" :inline="true">
+        <el-form-item class="mb-0 me-5 align-items-center" label="商户名称">
+          <el-input class="" style="width: 180px" v-model="companyName" placeholder="请输入商户名称" :size="size" />
+        </el-form-item>
+        <el-form-item class="mb-0 me-5 align-items-center" label="区域">
+          <OrgSelectTree v-model:value="orgId" style="width: 180px"></OrgSelectTree>
+        </el-form-item>
+        <el-form-item class="mb-0 me-5 align-items-center" label="菜系">
+          <DySelect
+            v-model="cateringStyle"
+            :formatRemoteData="(v:any)=>{return v?.list}"
+            :url="'sys/dict/getList?code=000010001&key=temp'"
+            :style="dySearchSelectStyle"
+            placeholder="请选择菜系"
+          ></DySelect>
+        </el-form-item>
+        <el-form-item class="mb-0 me-5 align-items-center" label="规模">
+          <DySelect
+            v-model="cateringScale"
+            :formatRemoteData="(v:any)=>{return v?.list}"
+            :url="'sys/dict/getList?code=000090001&key=temp'"
+            :style="dySearchSelectStyle"
+            placeholder="请选择规模"
+          ></DySelect>
+        </el-form-item>
+        <el-form-item class="mb-0 me-5 align-items-center" label="类型">
+          <DySelect
+            v-model="type"
+            :formatRemoteData="(v:any)=>{return v?.list}"
+            :url="'sys/dict/getList?code=000200000&key=temp'"
+            :style="dySearchSelectStyle"
+            placeholder="请选择类型"
+          ></DySelect>
+        </el-form-item>
+        <DateRangeSelect
+          v-model:date-value="date"
+          v-model:select-value="timeType"
+          :select-class="'mb-0 align-items-center'"
+          :date-class="'mb-0 mt-3 align-items-center'"
+          :style="dySearchSelectStyle"
+          :size="size"
+        />
+        <!-- <el-form-item class="mb-0 me-5 align-items-center" label="营业状态">
+          <el-select v-model="operateStatus" style="width: 150px" placeholder="请选择营业状态" :clearable="true">
+            <el-option value="0" label="营业"></el-option>
+            <el-option value="1" label="间休"></el-option>
+            <el-option value="2" label="停业"></el-option>
+          </el-select>
+        </el-form-item> -->
+        <el-form-item class="mb-0 me-0 align-items-center" :class="timeType == 4 ? `mt-3` : ''">
+          <el-button class="ms-3 mt-0 btn btn-sm btn-primary" @click="query">查询</el-button>
+          <el-button class="ms-3 mt-0 btn btn-sm btn-light-primary btn-outline" @click="reset">重置</el-button>
+          <el-button class="ms-3 mt-0 btn btn-sm btn-light-info btn-outline" @click="exported">导出</el-button>
+        </el-form-item>
+      </el-form>
+    </template>
+    <template #name="{ row }">
+      <span class="text-primary" @click="jump(row)" style="cursor: pointer">{{ row["company_name"] }}</span>
+    </template>
+
+    <template #action="{ row }">
+      <span class="table-action" @click="detail(row)">查看详情</span>
+    </template>
+  </VbDataTable>
+</template>

+ 236 - 0
src/views/analysisInfo/_WeekClean.vue

@@ -0,0 +1,236 @@
+<script setup lang="ts">
+import { ref, onMounted } from "vue"
+import moment from "moment"
+import Rs from "@/core/services/RequestService"
+import { useRouter } from "vue-router"
+const router = useRouter()
+const ringOptions = {
+  legend: { top: "center", bottom: "auto", right: "20%", orient: "vertical" },
+  pieCenter: ["40%", "50%"],
+  titleText: "",
+}
+const barOptions = {
+  grid: { top: 30, left: 15 },
+  legend: { show: false },
+  titleText: " ",
+}
+const timeType = ref(0)
+const date = ref<[Date, Date]>([new Date(), new Date()])
+const chartDatas = ref<Array<any>>([{}, {}, {}, {}])
+function queryChart(type: number) {
+  let url = ""
+  switch (type) {
+    case 0:
+      url = "sys/waitForClean/getOrgCleanStatus"
+
+      break
+    case 1:
+      url = "sys/waitForClean/getOrgWaitForCleanPie"
+      break
+  }
+  Rs.get(url).then((res) => {
+    chartDatas.value[type] = res.data
+    chartDatas.value[type].chartData.title = ""
+  })
+}
+
+const size = ref<any>("default")
+const dySearchSelectStyle = { width: "120px" }
+const companyName = ref("")
+const orgId = ref<string | null>(null)
+const type = ref("")
+const cateringScale = ref("")
+const cateringStyle = ref("")
+const queryParams = ref<any>({
+  time_type: timeType.value,
+})
+const cols = ref([
+  {
+    name: "序号",
+    field: "index",
+  },
+  {
+    name: "区域",
+    field: "org_name",
+  },
+  {
+    name: "商户名称",
+    field: "name",
+  },
+  {
+    name: "菜系",
+    field: "catering_style_name",
+  },
+  {
+    name: "规模",
+    field: "catering_scale_name",
+  },
+  {
+    name: "净化器数量",
+    field: "purifier_num",
+  },
+  {
+    name: "操作",
+    width: 150,
+    field: "action",
+  },
+])
+const jump = function (v: any) {
+  console.log("jump", v)
+  router.push({
+    path: "/goLineData/oilFumeConcentration",
+    query: {
+      comName: v.company_name,
+      company_id: v.company_id,
+      backNeed: 1,
+    },
+  })
+}
+
+function query() {
+  const params = {
+    time_type: timeType.value,
+    org_id: orgId.value,
+    name: companyName.value,
+    //operate_status: operateStatus.value,
+    type: type.value,
+    catering_scale: cateringScale.value,
+    catering_style: cateringStyle.value,
+    query_start_time: moment(date.value[0]).format("YYYYMMDD"),
+    query_end_time: moment(date.value[1]).format("YYYYMMDD"),
+  }
+  const keys = Object.keys(params)
+  keys.forEach((key) => {
+    if (params[key] == "" && key != "clean_flag" && key != "exceed_company_flag") {
+      delete params[key]
+    }
+  })
+  queryParams.value = params
+}
+function reset() {
+  type.value = ""
+  companyName.value = ""
+  orgId.value = "0"
+  type.value = ""
+  cateringScale.value = ""
+  cateringStyle.value = ""
+  timeType.value = 0
+  date.value = [new Date(), new Date()]
+  query()
+}
+function exported() {
+  const params = {
+    name: companyName.value,
+    catering_scale: cateringScale.value,
+    catering_style: cateringStyle.value,
+    type: type.value,
+    org_id: orgId.value,
+  }
+  let str = ""
+  for (const key in params) {
+    str += `${key}=${params[key]}&`
+  }
+  window.open("/api/sys/waitForClean/exportWaitCleanCompany?" + str)
+}
+const detail = function (row: any) {
+  router.push({
+    path: "/DataBoard/index/cleanList",
+    query: {
+      company_id: row.company_id,
+      type: "1",
+    },
+  })
+}
+
+function init() {
+  queryChart(0)
+  queryChart(1)
+}
+onMounted(init)
+</script>
+<template>
+  <el-row :gutter="20">
+    <el-col :span="12">
+      <div class="card card-bordered border-primary h-325px">
+        <div class="card-header bg-light-primary min-h-50px">
+          <div class="card-title">
+            <h3 class="text-primary">区域净化效能</h3>
+          </div>
+        </div>
+        <div class="card-body p-2">
+          <BaseChart :data="chartDatas[0]" type="bar" h="100%" :options="barOptions"></BaseChart>
+        </div>
+      </div>
+    </el-col>
+    <el-col :span="12">
+      <div class="card card-bordered border-primary h-325px">
+        <div class="card-header bg-light-primary min-h-50px">
+          <div class="card-title">
+            <h3 class="text-primary">一周内待清洗数量</h3>
+          </div>
+        </div>
+        <div class="card-body p-2">
+          <BaseChart :data="chartDatas[1]" type="ring" h="100%" :options="ringOptions"></BaseChart>
+        </div>
+      </div>
+    </el-col>
+  </el-row>
+  <VbDataTable
+    ref="table"
+    :header="cols"
+    url="sys/waitForClean/getComapnyForPage"
+    method="post"
+    :query-params="queryParams"
+    :has-checkbox="false"
+  >
+    <template v-slot:table-tool="">
+      <el-form class="align-items-center" :inline="true">
+        <el-form-item class="mb-0 me-5 align-items-center" label="商户名称">
+          <el-input class="" style="width: 180px" v-model="companyName" placeholder="请输入商户名称" :size="size" />
+        </el-form-item>
+        <el-form-item class="mb-0 me-5 align-items-center" label="区域">
+          <OrgSelectTree v-model:value="orgId" style="width: 180px"></OrgSelectTree>
+        </el-form-item>
+        <el-form-item class="mb-0 me-5 align-items-center" label="菜系">
+          <DySelect
+            v-model="cateringStyle"
+            :formatRemoteData="(v:any)=>{return v?.list}"
+            :url="'sys/dict/getList?code=000010001&key=temp'"
+            :style="dySearchSelectStyle"
+            placeholder="请选择菜系"
+          ></DySelect>
+        </el-form-item>
+        <el-form-item class="mb-0 me-5 align-items-center" label="规模">
+          <DySelect
+            v-model="cateringScale"
+            :formatRemoteData="(v:any)=>{return v?.list}"
+            :url="'sys/dict/getList?code=000090001&key=temp'"
+            :style="dySearchSelectStyle"
+            placeholder="请选择规模"
+          ></DySelect>
+        </el-form-item>
+        <el-form-item class="mb-0 me-5 align-items-center" label="类型">
+          <DySelect
+            v-model="type"
+            :formatRemoteData="(v:any)=>{return v?.list}"
+            :url="'sys/dict/getList?code=000200000&key=temp'"
+            :style="dySearchSelectStyle"
+            placeholder="请选择类型"
+          ></DySelect>
+        </el-form-item>
+        <el-form-item class="mb-0 me-0 align-items-center" :class="timeType == 4 ? `mt-3` : ''">
+          <el-button class="ms-3 mt-0 btn btn-sm btn-primary" @click="query">查询</el-button>
+          <el-button class="ms-3 mt-0 btn btn-sm btn-light-primary btn-outline" @click="reset">重置</el-button>
+          <el-button class="ms-3 mt-0 btn btn-sm btn-light-info btn-outline" @click="exported">导出</el-button>
+        </el-form-item>
+      </el-form>
+    </template>
+    <template #name="{ row }">
+      <span class="text-primary" @click="jump(row)" style="cursor: pointer">{{ row["company_name"] }}</span>
+    </template>
+
+    <template #action="{ row }">
+      <span class="table-action" @click="detail(row)">查看详情</span>
+    </template>
+  </VbDataTable>
+</template>

+ 12 - 2
src/views/analysisInfo/clean.vue

@@ -1,7 +1,17 @@
 <script setup lang="ts">
-import { defineProps, reactive, ref, toRefs } from "vue"
+import { ref } from "vue"
+import OverDate from "./_OverDate.vue"
+import WeekClean from "./_WeekClean.vue"
+const queryType = ref(0)
 </script>
 
 <template>
-  <div>clean</div>
+  <div class="mb-3">
+    <el-radio-group v-model="queryType" size="large">
+      <el-radio-button label="0">逾期商户</el-radio-button>
+      <el-radio-button label="1">一周待清洗</el-radio-button>
+    </el-radio-group>
+  </div>
+  <OverDate v-if="queryType == 0"></OverDate>
+  <WeekClean v-if="queryType == 1"></WeekClean>
 </template>

+ 3 - 1
src/views/dashboard/Screen/Alarm.vue

@@ -86,7 +86,9 @@ function scroll() {
   }
 }
 function init() {
-  Rs.post("sys/pushEvent/getList", { data: { pageIndex: 1, pageSize: props.max, params: {} } }).then((res) => {
+  Rs.post("sys/pushEvent/getList", {
+    data: { pageIndex: 1, pageSize: props.max, params: {}, successAlert: false },
+  }).then((res) => {
     alarmData.value = res.data
     if (alarmData.value.length > props.scrollLength) {
       clearInterval(scorllTimer)

+ 1 - 1
src/views/dashboard/Screen/Map.vue

@@ -87,7 +87,7 @@ const getCompany = () => {
 
   //   renderMap()
   // })
-  Rs.post("sys/query/getCompany", { data: companySearch }).then((res: any) => {
+  Rs.post("sys/query/getCompany", { data: companySearch, successAlert: false }).then((res: any) => {
     companyList.value = res.data
     companyTotal.value = Number(res.total)
     renderMap()

+ 12 - 41
src/views/overAnalysis/overTime.vue

@@ -16,13 +16,11 @@ const cols = ref<Array<Header>>([
   {
     name: "接入商户数",
     field: "total",
-    tdClass: "text-success",
     width: 120,
   },
   {
     name: "超标商户数",
     field: "exceed_total",
-    thClass: "text-warning",
   },
   {
     name: "油烟超标商户数",
@@ -60,11 +58,11 @@ const queryParams = ref({
 })
 const table = ref()
 
-function changeQueryType(type: number) {
-  queryType.value = type
-  name.value = type == 0 ? "区域" : type == 1 ? "菜系" : type == 2 ? "规模" : "类型"
+function changeQueryType() {
+  //queryType.value = type
+  name.value = queryType.value == 0 ? "区域" : queryType.value == 1 ? "菜系" : queryType.value == 2 ? "规模" : "类型"
   queryParams.value = {
-    query_type: type,
+    query_type: queryType.value,
     time_type: 0,
     query_start_time: moment().format("YYYYMMDD"),
     query_end_time: moment().format("YYYYMMDD"),
@@ -116,35 +114,13 @@ const detail = function (v: any) {
 </script>
 
 <template>
-  <div class="btn-group mb-3">
-    <button
-      class="btn btn-outline btn-light-primary"
-      :class="queryType == 0 ? 'active btn-active-primary' : ''"
-      @click="changeQueryType(0)"
-    >
-      区域
-    </button>
-    <button
-      class="btn btn-outline btn-light-primary"
-      :class="queryType == 1 ? 'active btn-active-primary' : ''"
-      @click="changeQueryType(1)"
-    >
-      菜系
-    </button>
-    <button
-      class="btn btn-outline btn-light-primary"
-      :class="queryType == 2 ? 'active btn-active-primary' : ''"
-      @click="changeQueryType(2)"
-    >
-      规模
-    </button>
-    <button
-      class="btn btn-outline btn-light-primary"
-      :class="queryType == 3 ? 'active btn-active-primary' : ''"
-      @click="changeQueryType(3)"
-    >
-      类型
-    </button>
+  <div class="mb-3">
+    <el-radio-group v-model="queryType" size="large" @change="changeQueryType">
+      <el-radio-button label="0">区域</el-radio-button>
+      <el-radio-button label="1">菜系</el-radio-button>
+      <el-radio-button label="2">规模</el-radio-button>
+      <el-radio-button label="3">类型</el-radio-button>
+    </el-radio-group>
   </div>
 
   <!-- 
@@ -181,12 +157,7 @@ const detail = function (v: any) {
         </el-form-item>
       </el-form>
     </template>
-    <template v-slot:name_header="">
-      <span class="text-danger text-center">{{ name }}</span>
-    </template>
-    <template #name="{ row }">
-      <div class="text-danger w-100">{{ row.name }}</div>
-    </template>
+
     <template #action="{ row }">
       <div class="text-danger text-center">
         <span class="table-action" @click="detail(row)">查看详情</span>