Browse Source

项目信息

CzRger 7 months ago
parent
commit
0141a37419

+ 5 - 0
.env.development

@@ -0,0 +1,5 @@
+# 本地环境
+NODE_ENV = development
+
+# 标题
+VITE_TITLE = 智慧搜索平台

+ 5 - 0
.env.production

@@ -0,0 +1,5 @@
+# 生产环境
+NODE_ENV = production
+
+# 标题
+VITE_TITLE = 智慧搜索平台

+ 25 - 0
.gitignore

@@ -0,0 +1,25 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+smart-search-web/

+ 4 - 0
README.md

@@ -0,0 +1,4 @@
+# nodejs: 18.17.1
+# 安装依赖: yarn
+# 启动项目: npm run dev
+# 打包部署: npm run build

+ 11 - 0
index.html

@@ -0,0 +1,11 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <title><{VITE_TITLE}></title>
+  </head>
+  <body>
+    <div id="app"></div>
+  </body>
+  <script type="module" src="/src/main.ts"></script>
+</html>

+ 36 - 0
package.json

@@ -0,0 +1,36 @@
+{
+  "name": "smart-search-web",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build --mode production",
+    "preview": "vite preview",
+    "serve": "vite --host 0.0.0.0"
+  },
+  "dependencies": {
+    "@types/node": "^20.14.12",
+    "axios": "^1.7.2",
+    "default-passive-events": "^2.0.0",
+    "fast-glob": "^3.3.2",
+    "pinia": "^2.1.7",
+    "rollup-plugin-visualizer": "^5.12.0",
+    "sass": "^1.77.8",
+    "uuid": "^10.0.0",
+    "vite-plugin-compression": "^0.5.1",
+    "vite-plugin-html-env": "^1.2.8",
+    "vite-plugin-svg-icons": "^2.0.1",
+    "vite-plugin-top-level-await": "^1.4.2",
+    "vue": "^3.4.31",
+    "vue-router": "^4.4.0",
+    "element-plus": "^2.3.7"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^5.0.5",
+    "postcss-px-to-viewport": "^1.1.1",
+    "typescript": "^5.2.2",
+    "vite": "^5.3.4",
+    "vue-tsc": "^2.0.24"
+  }
+}

+ 14 - 0
src/App.vue

@@ -0,0 +1,14 @@
+<template>
+  <div style="overflow: hidden; width: 100%; height: 100%;">
+    <el-config-provider :locale="zhCn">
+      <router-view/>
+    </el-config-provider>
+  </div>
+</template>
+
+<script setup lang="ts">
+import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 41 - 0
src/api/index.ts

@@ -0,0 +1,41 @@
+import HttpService from "./request";
+
+export const handle = ({url, params, method,config,filName}: any) => {
+  const httpService = new HttpService()
+  // @ts-ignore
+  return httpService[method.toLowerCase()](url, params,config,filName)
+}
+
+// @ts-ignore
+const files = import.meta.glob("/src/api/modules/**/*.ts")
+const apiModule: any = {}
+const apiRepeatMap = new Map()
+let num1 = 0
+let num2 = 0
+
+for (const [key, value] of Object.entries(files)) {
+  num1++
+  // @ts-ignore
+  value().then(res => {
+    for (const [apiKey, apiValue] of Object.entries(res)) {
+      if (apiKey !== '__tla') { // 过滤掉内部属性,非业务
+        if (apiRepeatMap.has(apiKey)) {
+          apiRepeatMap.set(apiKey, [...apiRepeatMap.get(apiKey), key])
+        } else {
+          apiRepeatMap.set(apiKey, [key])
+        }
+      }
+    }
+    Object.assign(apiModule, res)
+    num2++
+    if (num1 === num2) {
+      apiRepeatMap.forEach((v, k) => {
+        if (v.length > 1) {
+          console.error(`检测到接口${k}重复定义,路径为:`, v)
+        }
+      })
+    }
+  })
+}
+
+export default apiModule

+ 77 - 0
src/api/interceptors.ts

@@ -0,0 +1,77 @@
+import axios from 'axios';
+// import {notify} from '@/utils/notify'
+export class Interceptors {
+  public instance: any
+
+  constructor() {
+    this.instance = axios.create({timeout: 1000 * 300})
+    this.initInterceptors()
+  }
+
+  public getInterceptors() {
+    return this.instance
+  }
+
+  public initInterceptors() {
+    // this.instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8'
+    /**
+     * 请求拦截器
+     * 每次请求前,如果存在token则在请求头中携带token
+     */
+    this.instance.interceptors.request.use(
+      (config: any) => {
+        if (!config.headers.Authorization) {
+          const token = localStorage.getItem('ax_token');
+          if (token) {
+            config.headers.Authorization = token;
+          } else {
+            // @ts-ignore
+            delete config.headers.Authorization
+          }
+        }
+        return config;
+      },
+      (error: any) => {
+      }
+    );
+
+    // 响应拦截器
+    this.instance.interceptors.response.use(
+      // 请求成功
+      (res: any) => {
+        return Promise.resolve(res.data)
+      },
+      // 请求失败
+      (error: { response: any; }) => {
+        const { response } = error;
+        if (response) {
+          // 请求已发出,但是不在2xx的范围
+          this.errorHandle(response);
+          return Promise.reject(response.data);
+        } else {
+          //@ts-ignore
+          // notify.warning('网络连接异常,请稍后再试!');
+          // 抛出报错信息,在页面里需要接收
+          return Promise.reject(error);
+        }
+      });
+  }
+
+  private errorHandle(res: any) {
+    console.error('错误接口:' + res.data.path)
+    // 状态码判断
+    switch (res.status) {
+      case 401:
+        break;
+      case 403:
+        break;
+      case 404:
+        //@ts-ignore
+        // notify.warning('请求的资源不存在');
+        break;
+      default:
+        //@ts-ignore
+        // notify.warning('连接错误');
+    }
+  }
+}

+ 10 - 0
src/api/modules/global/global.ts

@@ -0,0 +1,10 @@
+import { handle } from '../../index'
+
+const suffix = 'api'
+
+// 模拟接口
+export const mockApi = (params: any) => handle({
+  url: `/${suffix}/prePlan/list`,
+  method: 'post',
+  params
+})

+ 68 - 0
src/api/request.ts

@@ -0,0 +1,68 @@
+import { Interceptors } from './interceptors';
+// import { ElMessage } from "element-plus";
+export class HttpRequest {
+  public axios: any
+  constructor() {
+    // 获取axios实例
+    this.axios = new Interceptors().getInterceptors();
+  }
+  public get(url: string, params: String, config: Object = {}) {
+    return new Promise((resolve, reject) => {
+      let paramUrl = url
+      if (params) {
+        paramUrl += `?${params}`
+      }
+      this.axios.get(paramUrl, {
+        ...config
+      }).then((res: any) => {
+        this.resultHandle(res, resolve, reject, url);
+      }).catch((err: { message: any; }) => {
+        console.log(err)
+        reject(err.message);
+      })
+    })
+  }
+
+  public post(url: string, params: Object, config: Object = {}) {
+    return new Promise((resolve, reject) => {
+      this.axios.post(url, params, {
+        ...config   //  导出添加的下载类型
+      }).then((res: any) => {
+        this.resultHandle(res, resolve, reject, url);
+      }).catch((err: { message: any; }) => {
+        reject(err.message);
+      })
+    })
+  }
+  public resultHandle(res: any, resolve: { (value: unknown): void; (value: unknown): void; (arg0: any): void; },reject: { (value: unknown): void; (value: unknown): void; (arg0: any): void; }, url: string) {
+    if (res) {
+      if (res.code === 200 || (res.size > 0 && res.type)) {   //  增加blob文件判断
+        resolve(res);
+      } else {
+        resolve(res);
+        // this.errorHandle(res, reject, url);
+      }
+    }
+  }
+  public errorHandle(res: any, reject: any, url: string) {
+    console.error('错误接口:' + url)
+    if (res.hasOwnProperty('message')) {
+      // ElMessage.warning(res.message);  // 统一谈服务端提示,我们提示统一由服务端提供
+    } else if (res.hasOwnProperty('msg')) {
+      // ElMessage.warning(res.msg);  // 统一谈服务端提示,我们提示统一由服务端提供
+    }
+    // 状态码判断
+    if (res) {
+      switch (res.code) {
+        case -102:
+          break;
+        case -152:
+          break;
+        default:
+          reject(res);
+      }
+    }
+  }
+}
+
+export default HttpRequest

+ 14 - 0
src/browerPatch.ts

@@ -0,0 +1,14 @@
+// @ts-ignore
+;(function () {
+    if (typeof EventTarget !== 'undefined') {
+        const func = EventTarget.prototype.addEventListener
+        EventTarget.prototype.addEventListener = function (type, fn, capture) {
+            ;(this as any).func = func
+            if (typeof capture !== 'boolean') {
+                capture = capture || {}
+                capture.passive = false
+            }
+            ;(this as any).func(type, fn, capture)
+        }
+    }
+})()

+ 53 - 0
src/components/SvgIcon/index.vue

@@ -0,0 +1,53 @@
+<template>
+  <svg aria-hidden="true" class="svg-icon" :style="`width: ${size}px;height: ${size}px;transform: rotate(${rotate}deg);`">
+    <use :xlink:href="symbolId" :fill="color" />
+  </svg>
+</template>
+
+
+<script lang="ts">
+  import {
+    defineComponent,
+    computed,
+  } from "vue";
+  export default defineComponent({
+    name: "SvgIcon",
+    components: {},
+    props: {
+      prefix: {
+        type: String,
+        default: 'icon'
+      },
+      name: {
+        type: String,
+        required: true
+      },
+      color: {
+        type: String,
+        default: ''
+      },
+      size: {
+        type: [Number, String],
+        default: '18'
+      },
+      rotate: {
+        type: [Number, String],
+        default: 0
+      }
+    },
+    setup(props, { emit }) {
+      const symbolId = computed(() => `#${props.prefix}-${props.name}`)
+      return {
+        symbolId
+      }
+    }
+  })
+</script>
+
+<style scoped>
+.svg-icon {
+  vertical-align: -0.15em;
+  overflow: hidden;
+  fill: currentColor;
+}
+</style>

+ 18 - 0
src/layout/index.vue

@@ -0,0 +1,18 @@
+<template>
+
+</template>
+
+<script setup lang="ts">
+import RouterViewCom from './router-view.vue'
+import {computed, getCurrentInstance, onMounted, reactive, watch} from "vue";
+import {useRouter, useRoute} from "vue-router";
+
+const {proxy} = getCurrentInstance()
+const router = useRouter()
+const route = useRoute()
+const state: any = reactive({
+})
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 42 - 0
src/layout/router-view.vue

@@ -0,0 +1,42 @@
+<template>
+  <router-view v-slot="{ Component }">
+<!--    <transition>-->
+      <keep-alive>
+        <component :is="Component" />
+      </keep-alive>
+<!--    </transition>-->
+  </router-view>
+</template>
+
+<script lang="ts">
+import {
+  defineComponent,
+  computed,
+  onMounted,
+  ref,
+  reactive,
+  watch,
+  getCurrentInstance,
+  ComponentInternalInstance,
+  toRefs,
+  nextTick
+} from 'vue'
+import {useRouter, useRoute} from 'vue-router'
+
+export default defineComponent({
+  name: '',
+  components: {},
+  setup() {
+    const router = useRouter();
+    const route = useRoute();
+    const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
+    const state = reactive({})
+    return {
+      ...toRefs(state)
+    }
+  },
+})
+</script>
+
+<style scoped lang="scss">
+</style>

+ 29 - 0
src/main.ts

@@ -0,0 +1,29 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+import router, {initMainRouter} from './router'
+import './style/index.scss'
+import 'virtual:svg-icons-register'    // 【svg-icons相关】
+import initComponent from '@/plugins/initComponent'
+import initProperties from '@/plugins/initProperties'
+import repeatFileValid from '@/plugins/repeatFileValid'
+import initDirect from '@/plugins/initDirect'
+import 'default-passive-events'
+import './browerPatch'
+import { createPinia } from 'pinia'
+import ElementPlus from 'element-plus'
+import 'element-plus/dist/index.css'
+
+
+repeatFileValid()
+await initMainRouter()  //  路由挂载前初始化路由表
+const app = createApp(App)
+app.use(createPinia())
+await initProperties(app)
+initComponent(app)
+app.use(initDirect)
+app.use(router)
+app.use(ElementPlus)
+app.mount('#app')
+// 设置为 true 以在浏览器开发工具的 performance/timeline 面板中启用对组件初始化、编译、渲染和更新的性能追踪。
+app.config.performance = true
+

+ 20 - 0
src/plugins/initComponent.ts

@@ -0,0 +1,20 @@
+import { App, Component } from 'vue'
+interface FileType {
+  [key: string]: Component
+}
+
+// @ts-ignore
+const Components: Record<string, FileType> = import.meta.glob('/src/components/**/*.vue', { eager: true })
+
+export default (app: App): void => {
+  // 因为通过 import.meta.globEager 返回的列表不能迭代所以直接使用 Object.keys 拿到 key 遍历
+  Object.keys(Components).forEach((c: string) => {
+    // const component = files[c]?.default
+    const component = Components[c] ? Components[c].default : null
+    // 组件内有注册过name才可自动挂载
+    if (component && component.name) {
+      // 挂载全局控件
+      app.component(component.name as string, component)
+    }
+  })
+}

+ 163 - 0
src/plugins/initDirect.ts

@@ -0,0 +1,163 @@
+import { App } from 'vue';
+
+// @ts-ignore
+interface type_OBj {
+	[key: string]: any
+}
+const screening_Brace = (el: HTMLElement) => {
+	return new Promise((s, e) => {
+		let sta = false, num = 1
+		if (typeof el.className === 'string' && el.className.indexOf('el-slider') != -1 || el.className.indexOf('label') != -1 || el.className.indexOf('title') != -1) {
+			num = 2
+			sta = true
+			return
+		} else if (num === 1) sta = false
+		s(sta)
+	})
+}
+export default {
+	install(Vue: App<Element>) {
+		Vue.directive('drag', {
+			mounted(el: HTMLElement, bind) {
+				el.onmousedown = async function (e: any) {
+					console.log(333, e.target?.className)
+					if (['INPUT', 'TEXTAREA', 'CANVAS'].includes(e.target.nodeName) || (typeof e.target?.className === "string" && e.target?.className?.includes('el-slider'))) {
+						return;
+					}
+					const sta = await screening_Brace(e.target)
+					if (sta) return
+					let elLeft = el.offsetLeft;
+					let elTop = el.offsetTop;
+					let x = e.clientX;
+					let y = e.clientY;
+					el.style.transition='unset'
+					document.onmousemove = (move: MouseEvent) => {
+						let left = elLeft - (x - move.clientX);
+						el.style.left = left + 'px'
+						el.style.right = 'unset'
+						let top = elTop - (y - move.clientY);
+						el.style.top = top + 'px'
+						el.style.bottom = 'unset'
+
+						if (window.getSelection()) {
+							// @ts-ignore
+							window.getSelection() ? window.getSelection().removeAllRanges() : null
+						}
+
+					}
+					document.onmouseup = (up: MouseEvent) => {
+						document.onmousemove = null;
+						document.onmouseup = null;
+					}
+				}
+			},
+			unmounted(el, bind) {
+				el.onmousedown = null;
+			}
+		}),
+		Vue.directive('wheelScale', {
+			mounted(el: HTMLElement, bind) {
+				const setTransformCss = (el, cssVarName) => {
+					let transformCssString = el.style.transform
+					let regScaleGlobal = /scale\(.*?[ )]*[)]+[ ]*/g //匹配 Scale属性 全局
+					if (regScaleGlobal.test(transformCssString)) {
+						transformCssString = transformCssString.replace(
+							regScaleGlobal,
+							` scale(var(${cssVarName})) `
+						)
+					} else {
+						transformCssString += " " + `scale(var(${cssVarName}))`
+					}
+					el.style.transform = transformCssString
+				}
+				const setVarScale = (el, cssVarName, scale) => {
+					let cssText = el.style.cssText
+					let cssTextList = cssText.split(";")
+					let isExist = false
+					let isExistIndex = -1
+					for (let index = 0; index < cssTextList.length; index++) {
+						const element = cssTextList[index]
+						if (element.includes(cssVarName + ":")) {
+							isExist = true
+							isExistIndex = index
+							break
+						}
+					}
+					if (isExist) {
+						cssTextList[isExistIndex] = `--scale: ${scale}`
+					} else {
+						cssTextList.push(`--scale: ${scale}`)
+						//   el.setAttribute("style", `--scale: ${scale}`)
+					}
+					cssText = cssTextList.join(";")
+					el.style.cssText = cssText
+				}
+				const { maxScale = 5, minScale = 0.5, auto = false }: any = bind.value || {}
+				let oldScale: number = 1
+				const cssVarName = "--scale"
+				if (el) {
+					if (auto) {
+						setTimeout(() => {
+							if (el.parentElement) {
+								const l = (el.parentElement.clientWidth - el.clientWidth) / 2
+								const t = (el.parentElement.clientHeight - el.clientHeight) / 2
+								if (l) {
+									el.style.left = `${l}px`
+								}
+								if (t) {
+									el.style.top = `${t}px`
+								}
+								const eRatio = el.clientWidth / el.clientHeight
+								const eParRatio = el.parentElement.clientWidth / el.parentElement.clientHeight
+								if (eRatio > eParRatio) {
+									oldScale = el.parentElement.clientWidth / el.clientWidth
+								} else {
+									oldScale = el.parentElement.clientHeight / el.clientHeight
+								}
+								if (oldScale > maxScale) {
+									oldScale = maxScale
+								}
+								if (oldScale < minScale) {
+									oldScale = minScale
+								}
+								setVarScale(el, cssVarName, oldScale)
+								setTransformCss(el, cssVarName)
+							}
+						}, 300)
+					}
+					el.onwheel = (e: any) => {
+						let _scale: any = el.style.getPropertyValue(cssVarName) || 1
+						if (e.wheelDelta > 0) {
+							_scale = _scale * 1 + 0.1
+						} else {
+							_scale = _scale * 1 - 0.1
+						}
+						// 现在缩放范围
+						if (_scale > maxScale) {
+							_scale = maxScale
+						} else if (_scale < minScale) {
+							_scale = minScale
+						}
+						setVarScale(el, cssVarName, _scale)
+						setTransformCss(el, cssVarName)
+						// 缩放改变回调函数
+						const wheelScaleChange = bind.value || null
+
+						if (wheelScaleChange instanceof Function && _scale != oldScale) {
+							oldScale = _scale
+							wheelScaleChange({
+								cssVarName,
+								currentScale: _scale,
+								maxScale,
+								minScale,
+							})
+						}
+					}
+				}
+			},
+			unmounted(el, bind) {
+				el.onwheel = null
+			}
+		})
+	}
+}

+ 19 - 0
src/plugins/initProperties.ts

@@ -0,0 +1,19 @@
+import {App, reactive} from 'vue'
+import * as api from '@/api/index'
+import * as util from '@/utils/util'
+import * as permissions from '@/utils/permissions'
+
+export default async (app: App) => {
+    app.config.globalProperties.$api = await api.default
+    app.config.globalProperties.$util = util
+    app.config.globalProperties.$permissions = permissions
+    app.config.globalProperties.$loading = reactive({
+        value: false,
+        show(){
+            this.value = true
+        },
+        hide(){
+            this.value = false
+        }
+    })
+}

+ 38 - 0
src/plugins/repeatFileValid.ts

@@ -0,0 +1,38 @@
+const SvgValid = () => {
+  // @ts-ignore
+  const svgs = import.meta.glob("/src/assets/svg/**/*.svg")
+  const svgModule: any = {}
+  const svgRepeatMap = new Map()
+  let num1 = 0
+  let num2 = 0
+  for (const [key, value] of Object.entries(svgs)) {
+    num1++
+    // @ts-ignore
+    value().then(res => {
+      const regex = /\/([^/]+)\.svg$/
+      for (const [svgKey, svgValue] of Object.entries(res)) {
+        // @ts-ignore
+        const result: any = svgValue.match(regex);
+        const text = result[1]; // 获取匹配到的文本
+        if (svgRepeatMap.has(text)) {
+          svgRepeatMap.set(text, [...svgRepeatMap.get(text), key])
+        } else {
+          svgRepeatMap.set(text, [key])
+        }
+      }
+      Object.assign(svgModule, res)
+      num2++
+      if (num1 === num2) {
+        svgRepeatMap.forEach((v, k) => {
+          if (v.length > 1) {
+            console.error(`检测到svg图标${k}重复定义,路径为:`, v)
+          }
+        })
+      }
+    })
+  }
+}
+
+export default (): void => {
+  SvgValid()
+}

+ 27 - 0
src/router/index.ts

@@ -0,0 +1,27 @@
+import {createRouter, createWebHistory} from 'vue-router'
+import staticRouter from './modules/static'
+import Temp404 from '@/views/global/temp/404.vue'
+
+const routes = [
+    ...staticRouter,
+    { path: '/:pathMatch(.*)*', name: 'NotFound', component: Temp404 }
+]
+
+const router = createRouter({
+    history: createWebHistory(),
+    routes,
+});
+
+router.beforeEach((to, from , next) => {
+    if (to.query.routeTitle) {
+        to.meta.title = to.query.routeTitle
+    }
+    document.title = to.meta?.title || import.meta.env.VITE_TITLE
+    next()
+})
+
+
+export const initMainRouter = async () => {
+
+}
+export default router;

+ 4 - 0
src/router/modules/static.ts

@@ -0,0 +1,4 @@
+// 不需要菜单管理配置的路由
+const staticRouter = [
+]
+export default staticRouter

+ 11 - 0
src/stores/app.ts

@@ -0,0 +1,11 @@
+import {defineStore} from "pinia";
+
+export const useAppStore = defineStore('app', {
+  state: () => ({
+    token: ''
+  }),
+  getters: {
+  },
+  actions: {
+  },
+})

+ 11 - 0
src/stores/dictionary-define.ts

@@ -0,0 +1,11 @@
+export const dictionaryDefine = {
+	//接口参数 : [ '字典数据list名', '字典数据Map名', '字段数据ObjMap名' ],
+	sys_role_type: ['sysRoleTypeList', 'sysRoleTypeMap'], //  角色所属分类
+}
+
+const stateMap = {}
+Object.keys(dictionaryDefine).map(i => {
+	stateMap[dictionaryDefine[i][0]] = []
+	stateMap[dictionaryDefine[i][1]] = new Map()
+})
+export const dictionary = stateMap

+ 12 - 0
src/stores/dictionary-other-define.ts

@@ -0,0 +1,12 @@
+export const dictionaryOtherDefine = {
+	//非字典接口的数据项,针对各业务功能内频繁需要调用的,尽量避免多余参数来回传递
+	itemType: ['itemTypeList', 'itemTypeMap', 'itemTypeObjMap'],	// 事项类型
+	jgType: ['jgTypeList', 'jgTypeMap', 'jgTypeObjMap'],	// 结果类型
+}
+
+const stateMap = {}
+Object.keys(dictionaryOtherDefine).map(i => {
+	stateMap[dictionaryOtherDefine[i][0]] = []
+	stateMap[dictionaryOtherDefine[i][1]] = new Map()
+})
+export const dictionaryOther = stateMap

+ 66 - 0
src/stores/dictionary.ts

@@ -0,0 +1,66 @@
+import * as api from '@/api/index'
+import { dictionary, dictionaryDefine } from './dictionary-define'
+import { dictionaryOther, dictionaryOtherDefine } from './dictionary-other-define'
+import { defineStore } from 'pinia'
+import {mockApi} from "@/api/modules/global/global";
+
+export const useDictionaryStore = defineStore('dictionary', {
+	state: () => ({
+		...dictionary,
+		...dictionaryOther,
+	}),
+	getters: {
+	},
+	actions: {
+		initDict(type) {
+			return new Promise((resolve, reject) => {
+				if (this[dictionaryDefine[type][0]].length === 0) {
+					api.default.mockApi().then((res: any) => {
+						console.log(res)
+						// const data = res.data
+						// const map = new Map()
+						// const objMap = new Map()
+						// if (data.length > 0) {
+						// 	data.forEach((v: any) => {
+						// 		v.selectLabel = v.dictLabel
+						// 		v.selectValue = v.dictValue
+						// 		map.set(v.dictValue, v.dictLabel)
+						// 		objMap.set(v.dictValue, v)
+						// 	})
+						// }
+						// this[dictionaryDefine[type][0]] = data
+						// this[dictionaryDefine[type][1]] = map
+						// this[dictionaryDefine[type]?.[2]] = objMap
+						resolve(res.data)
+					}).catch((e: any) => {
+						console.log('e: ', e);
+						reject('获取'+dictionaryDefine[type][0]+'字典错误')
+					})
+				} else {
+					resolve(this[dictionaryDefine[type][0]])
+				}
+			})
+		},
+		initOtherDict(type, data) {
+			return new Promise((resolve, reject) => {
+				if (this[dictionaryOtherDefine[type][0]].length === 0) {
+					const map = new Map()
+					const objMap = new Map()
+					if (data.length > 0) {
+						data.forEach((v: any) => {
+							v.selectLabel = v.dictLabel
+							v.selectValue = v.dictValue
+							map.set(v.dictValue, v.dictLabel)
+							objMap.set(v.dictValue, v)
+						})
+					}
+					this[dictionaryOtherDefine[type][0]] = data
+					this[dictionaryOtherDefine[type][1]] = map
+					this[dictionaryOtherDefine[type]?.[2]] = objMap
+				} else {
+					resolve(this[dictionaryOtherDefine[type][0]])
+				}
+			})
+		}
+	},
+})

+ 2 - 0
src/stores/index.ts

@@ -0,0 +1,2 @@
+export * from './app'
+export * from './dictionary'

+ 38 - 0
src/style/cus.scss

@@ -0,0 +1,38 @@
+:root {
+  --cus-main-color: #CE0022
+}
+
+.__tooltip {
+  position: relative;
+  background-color: #f9f9f9;
+  padding: 10px;
+  border-radius: 5px;
+  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
+  white-space: nowrap;
+  font-size: 12px;
+  color: #333333;
+  $afterWidth: 16px;
+  $afterHeight: 8px;
+  &:before {
+    content: '';
+    position: absolute;
+    background-color: #f9f9f9;
+    bottom: -$afterHeight;
+    width: $afterWidth;
+    height: $afterHeight;
+    left: calc((100% - #{$afterWidth}) / 2);
+    clip-path: polygon(0 0, 100% 0, 50% 100%);
+  }
+  &:after {
+    content: '';
+    position: absolute;
+    background-color: rgba(0, 0, 0, 0.1);
+    bottom: -$afterHeight - 1px;
+    width: $afterWidth + 2px;
+    height: $afterHeight + 1px;
+    left: calc((100% - #{$afterWidth} - 2px) / 2);
+    clip-path: polygon(0 0, 100% 0, 50% 100%);
+    filter: blur(2px);
+    z-index: -1;
+  }
+}

+ 28 - 0
src/style/index.scss

@@ -0,0 +1,28 @@
+@import './webkit-scrollbar';
+@import './cus';
+
+* {
+  outline: none;  // dom元素选中带边框
+  -webkit-user-drag: none;
+  box-sizing: border-box;
+  margin: 0;
+  padding: 0;
+}
+
+html, body, #app {
+  margin: 0;
+  padding: 0;
+  border: 0;
+  width: 100%;
+  height: 100%;
+}
+
+.tox-silver-sink {
+  //z-index: 99999 !important;
+  // 隐藏富文本APIkey提示
+  .tox-notifications-container {
+    width: 0;
+    height: 0;
+    overflow: hidden;
+  }
+}

+ 4 - 0
src/style/webkit-scrollbar.scss

@@ -0,0 +1,4 @@
+::-webkit-scrollbar { width: 4px; height: 6px; }
+::-webkit-scrollbar-track { width: 4px; background-color: transparent;-webkit-border-radius: 2em; -moz-border-radius: 2em; border-radius: 2em; }
+::-webkit-scrollbar-thumb { background-color: rgba(1, 15, 58, 0.2); background-clip: padding-box; min-height: 28px; -webkit-border-radius: 2em; -moz-border-radius: 2em; border-radius: 2em; }
+::-webkit-scrollbar-thumb:hover { background-color: rgba(1, 15, 58, 0.3); }

+ 3 - 0
src/utils/permissions.ts

@@ -0,0 +1,3 @@
+export const has = (value: any) => {
+
+}

+ 603 - 0
src/utils/util.ts

@@ -0,0 +1,603 @@
+export const isValue = (val: any) => {
+  if (val === null || val === undefined || (typeof val === 'string' && val.trim() === '') || (typeof val === 'object' && val?.length === 0)) {
+    return false
+  }
+  return true
+}
+
+export const structureParams = (array: Array<any>, attribute: string) => {
+  const endArray: any[] = []
+  array.forEach(v => {
+    endArray.push(v[attribute])
+  })
+  return endArray
+}
+
+export const replaceParams = (array: Array<any>, reArray: Array<any>, attribute: string, reAttribute: string) => {
+  const endArray: any[] = []
+  const endAllArray: any[] = []
+  array.forEach(v => {
+    reArray.forEach(rv => {
+      if (v === rv[attribute]) {
+        endArray.push(rv[reAttribute])
+        endAllArray.push(rv)
+      }
+    })
+  })
+  return {
+    replace: endArray,
+    all: endAllArray
+  }
+}
+
+export const copyObject = (ob: Object) => {
+  return JSON.parse(JSON.stringify(ob))
+}
+
+export const arrayToMap = (array: Array<any>, key: any) => {
+  const map = new Map()
+  array.forEach((v: any) => {
+    map.set(v[key], v)
+  })
+  return map
+}
+
+/**
+ * 通过某个字段在一个多级数组中查找数据
+ * @param data 目标数组,不能包含函数
+ * @param current 目标数据
+ * @param key 查找的字段
+ * @param children 子集集合字段
+ */
+export const findInListData = (data: Array<any>, current:any, key = "id", children = 'children') => {
+
+  for(let item of data){
+
+    if(item[key] && JSON.parse(JSON.stringify(item[key])) == JSON.parse(JSON.stringify(current))) return item
+
+    if(!!item[children] && Array.isArray(item[children]) && item[children].length > 0){
+
+      const findChildData: any = findInListData(item[children], current, key, children)
+
+      if(findChildData) return findChildData
+
+    }
+
+  }
+
+  return null
+}
+
+export const formatGetParam = (params: any) => {
+  let paramUrl = ''
+  Object.keys(params).forEach((v, i) => {
+    paramUrl += i === 0 ? `${v}=${encodeURIComponent(params[v])}` : `&${v}=${encodeURIComponent(params[v])}`
+  })
+  return paramUrl
+}
+
+export const formatTableHeadFilters = (arr: Array<any>, text = 'dictLabel', value = 'dictValue') => {
+  return arr.map(v => {
+    v.value = v[value]
+    v.text = v[text]
+    return v
+  })
+}
+
+export const YMDHms = (date: any) => {
+  const _date = new Date(date)
+  const Y = `${_date.getFullYear()}`;
+  const M = `${_date.getMonth() + 1 < 10 ? `0${_date.getMonth() + 1}` : _date.getMonth() + 1}`;
+  const D = `${_date.getDate() < 10 ? `0${_date.getDate()}` : _date.getDate()}`;
+  const H = `${_date.getHours() < 10 ? `0${_date.getHours()}` : _date.getHours()}`;
+  const m = `${_date.getMinutes() < 10 ? `0${_date.getMinutes()}` : _date.getMinutes()}`;
+  const s = _date.getSeconds() < 10 ? `0${_date.getSeconds()}` : _date.getSeconds();
+  return `${Y}-${M}-${D} ${H}:${m}:${s}`;
+}
+
+export const YM = (date: any, format = false) => {
+  const _date = new Date(date)
+  const Y = `${_date.getFullYear()}`;
+  const M = `${_date.getMonth() + 1 < 10 ? `0${_date.getMonth() + 1}` : _date.getMonth() + 1}`;
+  return format ? `${Y}年${M}月` : `${Y}-${M}`;
+}
+export const M = (date: any, format = false) => {
+  const _date = new Date(date)
+  const M = `${_date.getMonth() + 1 < 10 ? `0${_date.getMonth() + 1}` : _date.getMonth() + 1}`;
+  return format ? `${M}月` : `${M}`;
+}
+
+export const YMD = (date: any, format = false) => {
+  const _date = new Date(date)
+  const Y = `${_date.getFullYear()}`;
+  const M = `${_date.getMonth() + 1 < 10 ? `0${_date.getMonth() + 1}` : _date.getMonth() + 1}`;
+  const D = `${_date.getDate() < 10 ? `0${_date.getDate()}` : _date.getDate()}`;
+  return format ? `${Y}年${M}月${D}日` : `${Y}-${M}-${D}`;
+}
+
+export const Hms = (date: any, format = false) => {
+  const _date = new Date(date)
+  const H = `${_date.getHours() < 10 ? `0${_date.getHours()}` : _date.getHours()}`;
+  const m = `${_date.getMinutes() < 10 ? `0${_date.getMinutes()}` : _date.getMinutes()}`;
+  const s = _date.getSeconds() < 10 ? `0${_date.getSeconds()}` : _date.getSeconds();
+  return format ? `${H}时${m}分${s}秒` : `${H}:${m}:${s}`;
+}
+
+//防抖
+export const debounce = function (cb: any, ms = 0) {
+  let timer: any = null
+  return function () {
+    if (timer) clearTimeout(timer)
+    timer = setTimeout(() => {
+      //  @ts-ignore
+      cb.apply(this, arguments)
+      timer = null
+    }, ms)
+  }
+}
+
+export const comTime = (time: any, format = false) => {
+  const sAll = time
+  const d = Math.floor(sAll / (1000 * 60 * 60 * 24))
+  const h = Math.floor((sAll - d * (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
+  const m = Math.floor((sAll - d * (1000 * 60 * 60 * 24) - h * (1000 * 60 * 60)) / (1000 * 60))
+  const s = Math.floor((sAll - d * (1000 * 60 * 60 * 24) - h * (1000 * 60 * 60) - m * (1000 * 60)) / 1000)
+  let result: any = {d, h, m ,s}
+  if (format) {
+    result = ''
+    if (d > 0) {
+      result += d + '天'
+    }
+    if (d > 0 || h > 0) {
+      result += h + '时'
+    }
+    if (d > 0 || h > 0 || m > 0) {
+      result += m + '分'
+    }
+    result += s + '秒'
+  }
+  return result
+}
+
+export const comTimeByArea = (start: any, end: any, format = false, _D = '天', _H = '时', _M = '分', _S = '秒') => {
+  const sAll = new Date(end).getTime() - new Date(start).getTime()
+  const d = Math.floor(sAll / (1000 * 60 * 60 * 24))
+  const h = Math.floor((sAll - d * (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
+  const m = Math.floor((sAll - d * (1000 * 60 * 60 * 24) - h * (1000 * 60 * 60)) / (1000 * 60))
+  const s = Math.floor((sAll - d * (1000 * 60 * 60 * 24) - h * (1000 * 60 * 60) - m * (1000 * 60)) / 1000)
+  let result: any = {d, h, m ,s}
+  if (format) {
+    result = ''
+    if (d > 0) {
+      result += d + _D
+    }
+    if (d > 0 || h > 0) {
+      result += h + _H
+    }
+    if (d > 0 || h > 0 || m > 0) {
+      result += m + _M
+    }
+    result += s + _S
+  }
+  return result
+}
+
+export const deepAssign = (...obj: any) => {
+  const result = Object.assign({}, ...obj)
+  for (let item of obj) {
+    for (let [idx, val] of Object.entries(item)) {
+      if (val instanceof Array) {
+        result[idx] = val
+      } else if (val instanceof Object) {
+        result[idx] = deepAssign(result[idx], val)
+      }
+    }
+  }
+  return result
+}
+
+export const copy = (value: any) => {
+  const str = document.createElement('input')
+  str.setAttribute('value', value)
+  document.body.appendChild(str)
+  str.select()
+  document.execCommand('copy')
+  document.body.removeChild(str)
+  console.log(value)
+}
+
+/**
+ *
+ * @param precision 精度  1、0.1 、0.01……
+ * @param colorArr
+ * [
+ *    [20.1, '#111111'],
+ *    [20.3, '#dddddd'],
+ *    [20.7, '#eeeeee'],
+ * ]
+ * @return colorMap
+ * new Map([
+ *    [20.1, '#111111']
+ *    ……
+ *    [20.3, '#dddddd']
+ *    ……
+ *    [20.7, '#eeeeee']
+ * ])
+ */
+export const getGradientColorArray = (precision: any, colorArr: any) => {
+  // 将hex表示方式转换为rgb表示方式(这里返回rgb数组模式)
+  const colorRgb = (sColor: any) => {
+    const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
+    let _sColor = sColor.toLowerCase();
+    if (_sColor && reg.test(_sColor)) {
+      if (_sColor.length === 4) {
+        let sColorNew = "#";
+        for (let i = 1; i < 4; i += 1) {
+          sColorNew += _sColor.slice(i, i + 1).concat(_sColor.slice(i, i + 1));
+        }
+        _sColor = sColorNew;
+      }
+      //处理六位的颜色值
+      const sColorChange = [];
+      for (let i = 1; i < 7; i += 2) {
+        // @ts-ignore
+        sColorChange.push(parseInt("0x" + _sColor.slice(i, i + 2)));
+      }
+      return sColorChange;
+    } else {
+      return _sColor;
+    }
+  };
+  // 将rgb表示方式转换为hex表示方式
+  const colorHex = (rgb: any) => {
+    const _this = rgb;
+    const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
+    if (/^(rgb|RGB)/.test(_this)) {
+      const aColor = _this.replace(/(?:(|)|rgb|RGB)*/g, "").split(",");
+      let strHex = "#";
+      for (let i = 0; i < aColor.length; i++) {
+        let hex = Number(aColor[i]).toString(16);
+        hex = Number(hex) < 10 ? 0 + '' + hex : hex;// 保证每个rgb的值为2位
+        if (hex === "0") {
+          hex += hex;
+        }
+        strHex += hex;
+      }
+      if (strHex.length !== 7) {
+        strHex = _this;
+      }
+      return strHex;
+    } else if (reg.test(_this)) {
+      const aNum = _this.replace(/#/, "").split("");
+      if (aNum.length === 6) {
+        return _this;
+      } else if (aNum.length === 3) {
+        let numHex = "#";
+        for (let i = 0; i < aNum.length; i += 1) {
+          numHex += (aNum[i] + aNum[i]);
+        }
+        return numHex;
+      }
+    } else {
+      return _this;
+    }
+  }
+  const rgb2hex = (sRGB: any) => {
+    const reg = /^(RGB|rgb)\((\d+),\s*(\d+),\s*(\d+)\)$/
+    if (!reg.test(sRGB)) return sRGB
+    const rgbArr = sRGB.match(/\d+/g)
+    const resultRgbArr = rgbArr.map((v: any) => {
+      if (+v > 16) return (+v).toString(16)
+      return '0' + (+v).toString(16)
+    })
+    return '#' + resultRgbArr.join('')
+  }
+  const gradientColor = (startColor: any, endColor: any, step: any) => {
+    const startRGB = colorRgb(startColor);//转换为rgb数组模式
+    const startR = startRGB[0];
+    const startG = startRGB[1];
+    const startB = startRGB[2];
+    const endRGB = colorRgb(endColor);
+    const endR = endRGB[0];
+    const endG = endRGB[1];
+    const endB = endRGB[2];
+    const sR = (endR - startR) / step;//总差值
+    const sG = (endG - startG) / step;
+    const sB = (endB - startB) / step;
+    const colorArr = [];
+    for (let i = 0; i <= step; i++) {
+      //计算每一步的hex值
+      const hex = colorHex('rgb(' + parseInt((sR * i + startR)) + ',' + parseInt((sG * i + startG)) + ',' + parseInt((sB * i + startB)) + ')');
+      // @ts-ignore
+      colorArr.push(rgb2hex(hex));
+    }
+    return colorArr;
+  }
+  const colorMap = new Map()
+  colorArr.forEach((v: any, i: number) => {
+    if (i < colorArr.length - 1) {
+      const _arr = gradientColor(v[1], colorArr[i + 1][1], (Number(colorArr[i + 1][0]) - Number(v[0])) / precision)
+      _arr.forEach((cV, cI) => {
+        colorMap.set((Number(v[0]) + cI * precision).toFixed(String(precision).split('').filter(p => p === '0').length), cV)
+      })
+    } else {
+      colorMap.set(Number(v[0]).toFixed(String(precision).split('').filter(p => p === '0').length), v[1])
+    }
+  })
+  return colorMap
+}
+// 根据部门名称 寻找上级所有父节点名称
+//data:要遍历的数据, target:查找目标, result用于装查找结果的数组
+export const findParent = (data: any, target: any, result: any) => {
+  for (let item of data) {
+    if (item.deptName === target) {
+      //将查找到的目标数据加入结果数组中
+      //可根据需求unshift(item.id)或unshift(item)
+      result.unshift(item.deptName);
+      return true;
+    }
+    if (item.children && item.children.length > 0) {
+      let isFind = findParent(item.children, target, result);
+      if (isFind) {
+        result.unshift(item.deptName);
+        return true;
+      }
+    }
+  }
+  return false;
+};
+
+// 涉及到跨域问题,需要配nginx代理的url地址处理
+export const proxyNginxUrl = (url: string) => {
+  if (url) {
+    //  @ts-ignore
+    const apiMapper = window.cusConfig?.nginxApiMapper || new Map()
+    let newUrl = url
+    apiMapper.forEach((v: any, k: any) => {
+      if (url.includes(k)) {
+        newUrl =  v + url.substring(url.indexOf(k) + k.length, url.length)
+      }
+    })
+    return newUrl
+  }
+  return url
+};
+
+//  后端接口拦截 <  > 内的文本,正反向格式化为  /《/   /》/    res = true 为接收返回值进行格式化
+export const formatInputHtmlInterceptor = (html: string, res = true) => {
+  if (html) {
+    const map = new Map()
+    map.set('<', '_《_')
+    map.set('>', '_》_')
+    let nHtml = html.toString()
+    map.forEach((v, k) => {
+      if (res) {
+        nHtml = nHtml.replace(eval(`/${v}/g`), k)
+      } else {
+        nHtml = nHtml.replace(eval(`/${k}/g`), v)
+      }
+    })
+    return nHtml
+  }
+  return html
+}
+
+
+
+//生成从minNum到maxNum的随机数
+// export  const randomNum = (minNum: number,maxNum: number) => {
+//   return parseInt(String(Math.random() * (maxNum - minNum + 1) + minNum),10);
+// }
+
+export const randomNum = (min = 0, max = 0, decimal=0) => {
+  // 获取数值的小数部分
+  const getDecimalNum = (data: number) => {
+    return Number(data.toString().split('.')[1]);
+  }
+  let min_z = Math.trunc(min); // 最小值的整数部分
+  let max_z = Math.trunc(max); // 最大值的整数部分
+  // 判断是否存在小数部分,不存在的话为0
+  let min_x = isNaN(getDecimalNum(min)) ? 0 : getDecimalNum(min);  // 最小值的小数部分
+  let max_x = isNaN(getDecimalNum(max)) ? 0 : getDecimalNum(max);  // 最大值的小数部分
+
+  // 区分有小数和没小数的情况
+  if (min_x > 0 || max_x > 0 || decimal > 0) {
+    // 整数部分随机数
+    let z = parseInt(String(Math.random() * (max_z - min_z + 1) + min_z), 10);
+    // 小数部分随机数
+    let x = 0;
+    // 小数部分随机数最大位数
+    let max_decimal = min_x.toString().length > max_x.toString().length ? min_x.toString().length : max_x.toString().length;
+    max_decimal = decimal > max_decimal ? decimal : max_decimal;
+    // 判断随机出的整数部分,是否等于最小值或者最大值
+    if(z == min_z || z == max_z){
+      if(z == min_z){
+        // 整数部分随机数等于最小值,那么应该从最小值的小数部分开始,到小数位数的最大值随机就可以
+        x = parseInt(String(Math.random() * (Math.pow(10, max_decimal) - min_x) + min_x), 10);
+      }else{
+        // 整数部分随机数等于最大值,那么应该从0开始,到最大值小数部分
+        x = parseInt(String(Math.random() * (max_x + 1)), 10);
+      }
+    }else{
+      // 整数部分在最大最小值区间的,就从0到小数位数的最大值随机就可以
+      x = parseInt(String(Math.random() * (Math.pow(10, max_decimal))), 10);
+    }
+    return Number(`${z}.${x}`);
+  } else {
+    return parseInt(String(Math.random() * (max_z - min_z + 1) + min_z), 10);
+  }
+}
+
+/**
+ *
+ * @param val 处理的数值
+ * @param unit  单位,默认空字符
+ * @param digits  小数,默认1
+ */
+export const formatNumberUnit = (val, unit = '', digits = 1) => {
+  let num = ''
+  let str = ''
+  if (String(val).length < 5) {
+    num = val
+    str = unit
+  } else if (String(val).length < 9) {
+    num = Number((Number(val) / 10000).toFixed(digits)) / 1 + ''
+    str = '万' + unit
+  } else if (String(val).length < 13) {
+    num = Number((Number(val) / 10000 / 10000).toFixed(digits)) / 1 + ''
+    str = '亿' + unit
+  }
+  return {
+    num: num, unit: str
+  }
+}
+
+/**
+ * 将一串数字str按指定间隔n填充指定字符char
+ * @param str
+ * @param n
+ * @param char
+ */
+export const numberJoinChar = (str, n = 3, char = ',') => {
+  const _str = String(str)
+  let result = '';
+  for (let i = _str.length - 1; i >= 0; i--) {
+    result = _str[i] + result;
+    if ((_str.length - i) % n === 0 && i !== 0) {
+      result = char + result;
+    }
+  }
+  return result;
+}
+// 根据起始周几获取本周第一天日期
+export const getFirstDateOfWeek = (fDate, firstDayOfWeek) => {
+  const day = fDate.getDay();
+  const diff = (day < firstDayOfWeek ? 7 : 0) + day - firstDayOfWeek;
+  const firstDate = new Date(fDate.getFullYear(), fDate.getMonth(), fDate.getDate() - diff);
+  return firstDate;
+}
+
+// 根据起始周几获取本周最后一天日期
+export const getLastDateOfWeek = (lDate, lastDayOfWeek) => {
+  const day = lDate.getDay();
+  const diff = (day < lastDayOfWeek ? 7 : 0) + day - lastDayOfWeek;
+  const lastDate = new Date(lDate.getFullYear(), lDate.getMonth(), lDate.getDate() + (6 - diff));
+  return lastDate;
+}
+
+export const oneDayTime = 1000 * 60 * 60 * 24
+
+//  根据当天时间、起始周几、选的月,获取整月日期
+export const getMonthCalendarData = (d, fD, selectMonth = null) => {
+  const getYMDTime = (date) => {
+    const _date = new Date(date)
+    const Y = _date.getFullYear()
+    const M = _date.getMonth() + 1
+    const D = _date.getDate()
+    return new Date(`${Y}-${M}-${D}`).getTime()
+  }
+  const _date = selectMonth ? new Date(selectMonth) : new Date(d)
+  // d的月份有几天
+  const monthDaysTotal = new Date(_date.getFullYear(), _date.getMonth() + 1, 0).getDate()
+  // d的月份的第一天
+  const monthFirstDate = new Date(_date.getFullYear(), _date.getMonth(), 1)
+  // 日历第一天
+  const wholeFirstDay = getFirstDateOfWeek(monthFirstDate, fD)
+  // d的月份的最后一天
+  const monthLastDate = new Date(_date.getFullYear(), _date.getMonth(), monthDaysTotal)
+  // 日历最后一天
+  const wholeLastDay = getLastDateOfWeek(monthLastDate, fD)
+  const arr: any = []
+  let w: any = []
+  for (let i = wholeFirstDay.getTime(); i <= wholeLastDay.getTime(); i += oneDayTime) {
+    const t = new Date(i)
+    const obj: any = {
+      date: t,
+      dateStr: YMD(t),
+      dayStr: t.getDate(),
+      tom: getYMDTime(t) > getYMDTime(d)
+    }
+    if (getYMDTime(t) < monthFirstDate.getTime()) {
+      obj.last = true //  是补全的日历过去时间
+    } else if (getYMDTime(t) > monthLastDate.getTime()) {
+      obj.will = true //  是补全的日历未来时间
+    } else if (getYMDTime(t) === getYMDTime(d)) {
+      obj.today = true // 是传入的当前日期
+    }
+    w.push(obj)
+    if (w.length === 7) {
+      const wObj: any = {
+        calendar: w
+      }
+      arr.push(wObj)
+      w = []
+    }
+  }
+  return arr
+}
+
+// 根据起始周几获取一周的数组
+export const getWeekCN = (xq) => {
+  const m = new Map([
+    [0, '日'],
+    [1, '一'],
+    [2, '二'],
+    [3, '三'],
+    [4, '四'],
+    [5, '五'],
+    [6, '六'],
+  ])
+  const weekArray: any = [];
+  for (let i = 0; i < 7; i++) {
+    const index = (xq + i) % 7;
+    weekArray.push(m.get(index));
+  }
+  return weekArray
+}
+
+export const resetRgbaOpacity = (polyColor, opacity) => {
+  // 使用正则表达式匹配rgba颜色中的透明度部分
+  const rgbaRegex = /rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)/;
+  const matches = polyColor.match(rgbaRegex);
+  if (!matches) {
+    throw new Error('Invalid rgba string');
+  }
+  // 提取原来的透明度值,并将其乘以给定因子
+  const oldOpacity = parseFloat(matches[4]);
+  const newOpacity = Math.min(Math.max(oldOpacity * opacity, 0), 1); // 确保透明度值在0到1之间
+  // 重新组合rgba字符串
+  const newRgbaStr = `rgba(${matches[1]}, ${matches[2]}, ${matches[3]}, ${newOpacity})`;
+  return newRgbaStr;
+}
+
+export const randomColor = (opacity) => `rgba(${randomNum(0, 255)}, ${randomNum(0, 255)}, ${randomNum(0, 255)}, ${opacity ? opacity : randomNum(0.5, 1, 1)})`
+
+
+ //判断是否覆盖24小时 
+export const covers24Hours = (arr) =>{
+  if (arr.length == 0) return false;
+  let result = 0;
+  for (const item of arr) {
+    //获取时间
+    if (item.length == 0) return;
+    let start = item[0]?.split(":");
+    let end = item[1]?.split(":");
+    //把时间全部转化为秒
+    start = Number(start[0]) * 3600 + Number(start[1]) * 60 + Number(start[2]);
+    end = Number(end[0]) * 3600 + Number( end[1]) * 60 +  Number(end[2]);
+    if (start > end) {
+      end = end + 86400;
+    }
+    result += Math.abs(end - start);
+  }
+  console.log(result);
+  return result === 86400 || result === 86399;
+}
+
+export const toPhone = (phone) => {
+  let a: any = document.createElement('a')
+  a.style = {opacity: 0}
+  a.href = 'tel:' + phone
+  a.click()
+  document.removeChild(a)
+}

+ 8 - 0
src/views/global/temp/404.vue

@@ -0,0 +1,8 @@
+<template>
+</template>
+
+<script lang="ts">
+</script>
+
+<style scoped lang="scss">
+</style>

+ 30 - 0
tsconfig.json

@@ -0,0 +1,30 @@
+{
+  "compilerOptions": {
+    "noImplicitAny": false,
+    "allowJs": true,
+    "target": "ESNext",
+    "useDefineForClassFields": true,
+    "module": "ESNext",
+    "moduleResolution": "Node",
+    "strict": true,
+    "jsx": "preserve",
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "esModuleInterop": true,
+    "lib": ["ESNext", "DOM"],
+    "skipLibCheck": true,
+    "noEmit": true,
+    "baseUrl": "./",
+    "paths": {
+      "@/*": [
+        "./src/*"
+      ]
+    },
+    "types": [
+      "vite-plugin-svg-icons/client", // 【svg-icons相关】
+      "node"  // 【svg-icons相关】
+    ]
+  },
+  "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
+  "references": [{ "path": "./tsconfig.node.json" }]
+}

+ 9 - 0
tsconfig.node.json

@@ -0,0 +1,9 @@
+{
+  "compilerOptions": {
+    "composite": true,
+    "module": "ESNext",
+    "moduleResolution": "Node",
+    "allowSyntheticDefaultImports": true
+  },
+  "include": ["vite.config.ts"]
+}

+ 105 - 0
vite.config.ts

@@ -0,0 +1,105 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import topLevelAwait from 'vite-plugin-top-level-await'
+import { resolve } from 'path'
+import viteCompression from 'vite-plugin-compression';//Gzip
+import { visualizer } from "rollup-plugin-visualizer";
+import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' // 【svg-icons相关】
+import VitePluginHtmlEnv from 'vite-plugin-html-env'
+import postcsspxtoviewport from 'postcss-px-to-viewport'
+
+const time = new Date().getTime()
+export default defineConfig({
+  define: {
+    'process.env': process.env
+  },
+  plugins: [
+    vue(),
+    viteCompression(),
+    VitePluginHtmlEnv(),
+    visualizer(),
+    createSvgIconsPlugin({
+      // 指定需要缓存的图标文件夹
+      iconDirs: [resolve(process.cwd(), 'src/assets/svg')],
+      // 指定symbolId格式
+      symbolId: 'icon-[dir]-[name]',
+      // svgoOptions: {
+      //   plugins: [
+      //     {
+      //       name: 'removeAttrs',
+      //       params: {
+      //         attrs: ['class', 'data-name', 'fill', 'stroke']
+      //       }
+      //     }
+      //   ]
+      // }
+    }),
+
+    topLevelAwait({
+      promiseExportName: '__tla',
+      promiseImportName: i => `__tla_${i}`
+    })
+  ],
+  base: '/',
+  resolve: {
+    alias: {
+      '@': resolve(__dirname, 'src'),
+    },
+  },
+  server: {
+    port: 3853,
+    host: '0.0.0.0',
+    open: true,
+    strictPort: false,
+    proxy: {
+      '/api': {
+        target: 'http://8.130.72.63:18085/',
+        changeOrigin: true,
+        rewrite: (path) => {
+          return path.replace(/^\/api/, '')
+        }
+      }
+    }
+  },
+  css: {
+    postcss: {
+      plugins: [
+        postcsspxtoviewport({
+          unitToConvert: 'px',
+          viewportWidth: 350,
+          unitPrecision: 5, // 单位转换后保留的精度
+          propList: ['*'], // 能转化为vw的属性列表
+          viewportUnit: 'vw', // 希望使用的视口单位
+          fontViewportUnit: 'vw', // 字体使用的视口单位
+          selectorBlackList: ['ignore-'], // 需要忽略的CSS选择器,不会转为视口单位,使用原有的px等单位。
+          minPixelValue: 1, // 设置最小的转换数值,如果为1的话,只有大于1的值会被转换
+          mediaQuery: true, // 媒体查询里的单位是否需要转换单位
+          replace: true, //  是否直接更换属性值,而不添加备用属性
+          exclude: [], // 忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件
+          include: [], // 如果设置了include,那将只有匹配到的文件才会被转换
+          landscape: false, // 是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape)
+          landscapeUnit: 'vw', // 横屏时使用的单位
+          landscapeWidth: 1628, // 横屏时使用的视口宽度
+        }),
+      ]
+    }
+  },
+  build: {
+    outDir: "smart-search-web",
+    rollupOptions: {//分包优化
+      output: {
+        manualChunks(id) {
+          if (id.includes('node_modules')) {
+            return time + id.toString().split('node_modules/')[1].split('/')[0].toString();
+          } else {
+            return time + id.toString();
+          }
+        }
+      }
+    }
+  },
+  publicDir: 'src/out',
+  optimizeDeps: {
+    include: []
+  }
+} as any)

File diff suppressed because it is too large
+ 2958 - 0
yarn.lock