第一次
This commit is contained in:
		
						commit
						89c09e5a6b
					
				| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
VITE_APP_ENV=development
 | 
			
		||||
VITE_APP_PORT=9527
 | 
			
		||||
VITE_DROP_CONSOLE=false
 | 
			
		||||
 | 
			
		||||
# axios
 | 
			
		||||
VITE_APP_BASE_API=/api
 | 
			
		||||
VITE_APP_PROXY_URL=http://172.10.10.93:1233
 | 
			
		||||
 | 
			
		||||
#crypto js 前后端需保持一致
 | 
			
		||||
# VITE_APP_CRYPTO_JS_SECRET_KEY=f0234d57c311beb2
 | 
			
		||||
# VITE_APP_CRYPTO_JS_SECRET_IV=eb7905b31669ad1e
 | 
			
		||||
VITE_APP_CRYPTO_JS_SECRET_KEY=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCo58dwFmtTkiA4bj/FiBqlYsiKOugT/WCQRYFPY49A7y6VEswzMihgxSz0B/UDdekM9PDsKy06Gjy8BpWu+ikR1ms+/PbMJOmw5jGUeiswf8uqJDDquHG5oJJk0o7J8/1JvzmpbN/Ctjcm2yUscTfG2WvvY0ViwnptYU7+ZkRcuQIDAQAB
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
VITE_APP_ENV=production
 | 
			
		||||
VITE_APP_PORT=9528
 | 
			
		||||
VITE_DROP_CONSOLE=true
 | 
			
		||||
 | 
			
		||||
# axios
 | 
			
		||||
VITE_APP_BASE_API=/api
 | 
			
		||||
VITE_APP_PROXY_URL=https://localhost:8083
 | 
			
		||||
 | 
			
		||||
#crypto js 前后端需保持一致
 | 
			
		||||
# VITE_APP_CRYPTO_JS_SECRET_KEY=f0234d57c311beb2
 | 
			
		||||
# VITE_APP_CRYPTO_JS_SECRET_IV=eb7905b31669ad1e
 | 
			
		||||
VITE_APP_CRYPTO_JS_SECRET_KEY=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCo58dwFmtTkiA4bj/FiBqlYsiKOugT/WCQRYFPY49A7y6VEswzMihgxSz0B/UDdekM9PDsKy06Gjy8BpWu+ikR1ms+/PbMJOmw5jGUeiswf8uqJDDquHG5oJJk0o7J8/1JvzmpbN/Ctjcm2yUscTfG2WvvY0ViwnptYU7+ZkRcuQIDAQAB
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
# 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?
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
# Vue 3 + TypeScript + Vite
 | 
			
		||||
 | 
			
		||||
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
 | 
			
		||||
 | 
			
		||||
## Recommended IDE Setup
 | 
			
		||||
 | 
			
		||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
 | 
			
		||||
 | 
			
		||||
## Type Support For `.vue` Imports in TS
 | 
			
		||||
 | 
			
		||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types.
 | 
			
		||||
 | 
			
		||||
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
 | 
			
		||||
 | 
			
		||||
1. Disable the built-in TypeScript Extension
 | 
			
		||||
   1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette
 | 
			
		||||
   2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
 | 
			
		||||
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="UTF-8" />
 | 
			
		||||
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
			
		||||
    <title>管理后台</title>
 | 
			
		||||
  </head>
 | 
			
		||||
  <body>
 | 
			
		||||
    <div id="app"></div>
 | 
			
		||||
    <script type="module" src="/src/main.ts"></script>
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,40 @@
 | 
			
		|||
{
 | 
			
		||||
  "name": "web",
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "version": "0.0.0",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev": "vite --mode development",
 | 
			
		||||
    "pro": "vite --mode production",
 | 
			
		||||
    "build": "vite build --mode production",
 | 
			
		||||
    "preview": "vite preview"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@amap/amap-jsapi-loader": "^1.0.1",
 | 
			
		||||
    "@types/node": "^20.5.0",
 | 
			
		||||
    "@vitejs/plugin-vue-jsx": "^3.0.1",
 | 
			
		||||
    "ant-design-vue": "4.x",
 | 
			
		||||
    "axios": "^1.4.0",
 | 
			
		||||
    "crypto-js": "^4.1.1",
 | 
			
		||||
    "echarts": "^5.4.3",
 | 
			
		||||
    "jsencrypt": "^3.3.2",
 | 
			
		||||
    "lodash-es": "^4.17.21",
 | 
			
		||||
    "nprogress": "^0.2.0",
 | 
			
		||||
    "pinia": "^2.1.6",
 | 
			
		||||
    "pinia-plugin-persistedstate": "^3.2.0",
 | 
			
		||||
    "terser": "^5.19.2",
 | 
			
		||||
    "vue": "^3.3.4",
 | 
			
		||||
    "vue-router": "4"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/crypto-js": "^4.1.1",
 | 
			
		||||
    "@types/lodash-es": "^4.17.12",
 | 
			
		||||
    "@types/nprogress": "^0.2.3",
 | 
			
		||||
    "@vitejs/plugin-vue": "^4.2.3",
 | 
			
		||||
    "less": "^4.2.0",
 | 
			
		||||
    "sass": "^1.65.1",
 | 
			
		||||
    "typescript": "^5.0.2",
 | 
			
		||||
    "vite": "^4.4.5",
 | 
			
		||||
    "vue-tsc": "^1.8.5"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 1.5 KiB  | 
| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <!-- <a-config-provider> -->
 | 
			
		||||
  <router-view></router-view>
 | 
			
		||||
  <!-- </a-config-provider> -->
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts"></script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
body {
 | 
			
		||||
  font-family: AliBaBaPuHuTi, serif;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 46 KiB  | 
| 
						 | 
				
			
			@ -0,0 +1,4 @@
 | 
			
		|||
@font-face {
 | 
			
		||||
    font-family: AliBaBaPuHuTi;
 | 
			
		||||
    src: url('../font/AlibabaPuHuiTi-2-65-Medium.ttf');
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,53 @@
 | 
			
		|||
/* http://meyerweb.com/eric/tools/css/reset/ */
 | 
			
		||||
/* v1.0 | 20080212 */
 | 
			
		||||
 | 
			
		||||
html, body, div, span, applet, object, iframe,
 | 
			
		||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
 | 
			
		||||
a, abbr, acronym, address, big, cite, code,
 | 
			
		||||
del, dfn, em, font, img, ins, kbd, q, s, samp,
 | 
			
		||||
small, strike, strong, sub, sup, tt, var,
 | 
			
		||||
b, u, i, center,
 | 
			
		||||
dl, dt, dd, ol, ul, li,
 | 
			
		||||
fieldset, form, label, legend,
 | 
			
		||||
table, caption, tbody, tfoot, thead, tr, th, td {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    border: 0;
 | 
			
		||||
    outline: 0;
 | 
			
		||||
    font-size: 100%;
 | 
			
		||||
    vertical-align: baseline;
 | 
			
		||||
    background: transparent;
 | 
			
		||||
}
 | 
			
		||||
body {
 | 
			
		||||
    line-height: 1;
 | 
			
		||||
}
 | 
			
		||||
ol, ul {
 | 
			
		||||
    list-style: none;
 | 
			
		||||
}
 | 
			
		||||
blockquote, q {
 | 
			
		||||
    quotes: none;
 | 
			
		||||
}
 | 
			
		||||
blockquote:before, blockquote:after,
 | 
			
		||||
q:before, q:after {
 | 
			
		||||
    content: '';
 | 
			
		||||
    content: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* remember to define focus styles! */
 | 
			
		||||
:focus {
 | 
			
		||||
    outline: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* remember to highlight inserts somehow! */
 | 
			
		||||
ins {
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
del {
 | 
			
		||||
    text-decoration: line-through;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* tables still need 'cellspacing="0"' in the markup */
 | 
			
		||||
table {
 | 
			
		||||
    border-collapse: collapse;
 | 
			
		||||
    border-spacing: 0;
 | 
			
		||||
}
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 7.6 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 7.0 KiB  | 
| 
						 | 
				
			
			@ -0,0 +1,431 @@
 | 
			
		|||
/* flex */
 | 
			
		||||
.flx-center {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
}
 | 
			
		||||
.flx-justify-between {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
}
 | 
			
		||||
.flx-align-center {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* clearfix */
 | 
			
		||||
.clearfix::after {
 | 
			
		||||
  display: block;
 | 
			
		||||
  height: 0;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  clear: both;
 | 
			
		||||
  content: "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 文字单行省略号 */
 | 
			
		||||
.sle {
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  text-overflow: ellipsis;
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 文字多行省略号 */
 | 
			
		||||
.mle {
 | 
			
		||||
  display: -webkit-box;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  -webkit-box-orient: vertical;
 | 
			
		||||
  -webkit-line-clamp: 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 文字多了自动換行 */
 | 
			
		||||
.break-word {
 | 
			
		||||
  word-break: break-all;
 | 
			
		||||
  word-wrap: break-word;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* fade-transform */
 | 
			
		||||
.fade-transform-leave-active,
 | 
			
		||||
.fade-transform-enter-active {
 | 
			
		||||
  transition: all 0.2s;
 | 
			
		||||
}
 | 
			
		||||
.fade-transform-enter-from {
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
  transition: all 0.2s;
 | 
			
		||||
  transform: translateX(-30px);
 | 
			
		||||
}
 | 
			
		||||
.fade-transform-leave-to {
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
  transition: all 0.2s;
 | 
			
		||||
  transform: translateX(30px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* breadcrumb-transform */
 | 
			
		||||
.breadcrumb-enter-active {
 | 
			
		||||
  transition: all 0.2s;
 | 
			
		||||
}
 | 
			
		||||
.breadcrumb-enter-from,
 | 
			
		||||
.breadcrumb-leave-active {
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
  transform: translateX(10px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* scroll bar */
 | 
			
		||||
::-webkit-scrollbar {
 | 
			
		||||
  width: 6px;
 | 
			
		||||
  height: 6px;
 | 
			
		||||
}
 | 
			
		||||
::-webkit-scrollbar-thumb {
 | 
			
		||||
  background-color: var(--el-border-color-darker);
 | 
			
		||||
  border-radius: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* nprogress */
 | 
			
		||||
#nprogress .bar {
 | 
			
		||||
  background: var(--el-color-primary) !important;
 | 
			
		||||
}
 | 
			
		||||
#nprogress .spinner-icon {
 | 
			
		||||
  border-top-color: var(--el-color-primary) !important;
 | 
			
		||||
  border-left-color: var(--el-color-primary) !important;
 | 
			
		||||
}
 | 
			
		||||
#nprogress .peg {
 | 
			
		||||
  box-shadow: 0 0 10px var(--el-color-primary), 0 0 5px var(--el-color-primary) !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 外边距、内边距全局样式 */
 | 
			
		||||
@for $i from 0 through 40 {
 | 
			
		||||
  .mt#{$i} {
 | 
			
		||||
    margin-top: #{$i}px !important;
 | 
			
		||||
  }
 | 
			
		||||
  .mr#{$i} {
 | 
			
		||||
    margin-right: #{$i}px !important;
 | 
			
		||||
  }
 | 
			
		||||
  .mb#{$i} {
 | 
			
		||||
    margin-bottom: #{$i}px !important;
 | 
			
		||||
  }
 | 
			
		||||
  .ml#{$i} {
 | 
			
		||||
    margin-left: #{$i}px !important;
 | 
			
		||||
  }
 | 
			
		||||
  .pt#{$i} {
 | 
			
		||||
    padding-top: #{$i}px !important;
 | 
			
		||||
  }
 | 
			
		||||
  .pr#{$i} {
 | 
			
		||||
    padding-right: #{$i}px !important;
 | 
			
		||||
  }
 | 
			
		||||
  .pb#{$i} {
 | 
			
		||||
    padding-bottom: #{$i}px !important;
 | 
			
		||||
  }
 | 
			
		||||
  .pl#{$i} {
 | 
			
		||||
    padding-left: #{$i}px !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*  -- 内外边距 -- */
 | 
			
		||||
 | 
			
		||||
.margin-0 {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-xs {
 | 
			
		||||
  margin: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-sm {
 | 
			
		||||
  margin: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin {
 | 
			
		||||
  margin: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-lg {
 | 
			
		||||
  margin: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-xl {
 | 
			
		||||
  margin: 25px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-top-xs {
 | 
			
		||||
  margin-top: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-top-sm {
 | 
			
		||||
  margin-top: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-top {
 | 
			
		||||
  margin-top: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-top-lg {
 | 
			
		||||
  margin-top: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-top-xl {
 | 
			
		||||
  margin-top: 25px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-right-xs {
 | 
			
		||||
  margin-right: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-right-sm {
 | 
			
		||||
  margin-right: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-right {
 | 
			
		||||
  margin-right: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-right-lg {
 | 
			
		||||
  margin-right: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-right-xl {
 | 
			
		||||
  margin-right: 25px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-bottom-xs {
 | 
			
		||||
  margin-bottom: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-bottom-sm {
 | 
			
		||||
  margin-bottom: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-bottom {
 | 
			
		||||
  margin-bottom: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-bottom-lg {
 | 
			
		||||
  margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-bottom-xl {
 | 
			
		||||
  margin-bottom: 25px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-left-xs {
 | 
			
		||||
  margin-left: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-left-sm {
 | 
			
		||||
  margin-left: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-left {
 | 
			
		||||
  margin-left: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-left-lg {
 | 
			
		||||
  margin-left: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-left-xl {
 | 
			
		||||
  margin-left: 25px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-lr-xs {
 | 
			
		||||
  margin-left: 5px;
 | 
			
		||||
  margin-right: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-lr-sm {
 | 
			
		||||
  margin-left: 10px;
 | 
			
		||||
  margin-right: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-lr {
 | 
			
		||||
  margin-left: 15px;
 | 
			
		||||
  margin-right: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-lr-lg {
 | 
			
		||||
  margin-left: 20px;
 | 
			
		||||
  margin-right: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-lr-xl {
 | 
			
		||||
  margin-left: 25px;
 | 
			
		||||
  margin-right: 25px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-tb-xs {
 | 
			
		||||
  margin-top: 5px;
 | 
			
		||||
  margin-bottom: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-tb-sm {
 | 
			
		||||
  margin-top: 10px;
 | 
			
		||||
  margin-bottom: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-tb {
 | 
			
		||||
  margin-top: 15px;
 | 
			
		||||
  margin-bottom: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-tb-lg {
 | 
			
		||||
  margin-top: 20px;
 | 
			
		||||
  margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.margin-tb-xl {
 | 
			
		||||
  margin-top: 25px;
 | 
			
		||||
  margin-bottom: 25px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-0 {
 | 
			
		||||
  padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-xs {
 | 
			
		||||
  padding: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-sm {
 | 
			
		||||
  padding: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding {
 | 
			
		||||
  padding: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-lg {
 | 
			
		||||
  padding: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-xl {
 | 
			
		||||
  padding: 25px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-top-xs {
 | 
			
		||||
  padding-top: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-top-sm {
 | 
			
		||||
  padding-top: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-top {
 | 
			
		||||
  padding-top: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-top-lg {
 | 
			
		||||
  padding-top: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-top-xl {
 | 
			
		||||
  padding-top: 25px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-right-xs {
 | 
			
		||||
  padding-right: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-right-sm {
 | 
			
		||||
  padding-right: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-right {
 | 
			
		||||
  padding-right: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-right-lg {
 | 
			
		||||
  padding-right: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-right-xl {
 | 
			
		||||
  padding-right: 25px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-bottom-xs {
 | 
			
		||||
  padding-bottom: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-bottom-sm {
 | 
			
		||||
  padding-bottom: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-bottom {
 | 
			
		||||
  padding-bottom: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-bottom-lg {
 | 
			
		||||
  padding-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-bottom-xl {
 | 
			
		||||
  padding-bottom: 25px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-left-xs {
 | 
			
		||||
  padding-left: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-left-sm {
 | 
			
		||||
  padding-left: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-left {
 | 
			
		||||
  padding-left: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-left-lg {
 | 
			
		||||
  padding-left: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-left-xl {
 | 
			
		||||
  padding-left: 25px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-lr-xs {
 | 
			
		||||
  padding-left: 5px;
 | 
			
		||||
  padding-right: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-lr-sm {
 | 
			
		||||
  padding-left: 10px;
 | 
			
		||||
  padding-right: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-lr {
 | 
			
		||||
  padding-left: 15px;
 | 
			
		||||
  padding-right: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-lr-lg {
 | 
			
		||||
  padding-left: 20px;
 | 
			
		||||
  padding-right: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-lr-xl {
 | 
			
		||||
  padding-left: 25px;
 | 
			
		||||
  padding-right: 25px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-tb-xs {
 | 
			
		||||
  padding-top: 5px;
 | 
			
		||||
  padding-bottom: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-tb-sm {
 | 
			
		||||
  padding-top: 10px;
 | 
			
		||||
  padding-bottom: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-tb {
 | 
			
		||||
  padding-top: 15px;
 | 
			
		||||
  padding-bottom: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-tb-lg {
 | 
			
		||||
  padding-top: 20px;
 | 
			
		||||
  padding-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.padding-tb-xl {
 | 
			
		||||
  padding-top: 25px;
 | 
			
		||||
  padding-bottom: 25px;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,4 @@
 | 
			
		|||
@font-face {
 | 
			
		||||
  font-family: AliBaBaPuHuTi;
 | 
			
		||||
  src: url('../font/AlibabaPuHuiTi-2-65-Medium.ttf');
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,4 @@
 | 
			
		|||
@mixin wh($w, $h) {
 | 
			
		||||
  width: $w;
 | 
			
		||||
  height: $h;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,53 @@
 | 
			
		|||
/* http://meyerweb.com/eric/tools/css/reset/ */
 | 
			
		||||
/* v1.0 | 20080212 */
 | 
			
		||||
 | 
			
		||||
html, body, div, span, applet, object, iframe,
 | 
			
		||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
 | 
			
		||||
a, abbr, acronym, address, big, cite, code,
 | 
			
		||||
del, dfn, em, font, img, ins, kbd, q, s, samp,
 | 
			
		||||
small, strike, strong, sub, sup, tt, var,
 | 
			
		||||
b, u, i, center,
 | 
			
		||||
dl, dt, dd, ol, ul, li,
 | 
			
		||||
fieldset, form, label, legend,
 | 
			
		||||
table, caption, tbody, tfoot, thead, tr, th, td {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  border: 0;
 | 
			
		||||
  outline: 0;
 | 
			
		||||
  font-size: 100%;
 | 
			
		||||
  vertical-align: baseline;
 | 
			
		||||
  background: transparent;
 | 
			
		||||
}
 | 
			
		||||
body {
 | 
			
		||||
  line-height: 1;
 | 
			
		||||
}
 | 
			
		||||
ol, ul {
 | 
			
		||||
  list-style: none;
 | 
			
		||||
}
 | 
			
		||||
blockquote, q {
 | 
			
		||||
  quotes: none;
 | 
			
		||||
}
 | 
			
		||||
blockquote:before, blockquote:after,
 | 
			
		||||
q:before, q:after {
 | 
			
		||||
  content: '';
 | 
			
		||||
  content: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* remember to define focus styles! */
 | 
			
		||||
:focus {
 | 
			
		||||
  outline: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* remember to highlight inserts somehow! */
 | 
			
		||||
ins {
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
}
 | 
			
		||||
del {
 | 
			
		||||
  text-decoration: line-through;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* tables still need 'cellspacing="0"' in the markup */
 | 
			
		||||
table {
 | 
			
		||||
  border-collapse: collapse;
 | 
			
		||||
  border-spacing: 0;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
/* global css variable */
 | 
			
		||||
$primary-color: var(--el-color-primary);
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
/* 存放css变量 */
 | 
			
		||||
$primary-color: red;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 496 B  | 
| 
						 | 
				
			
			@ -0,0 +1,104 @@
 | 
			
		|||
import axios, {
 | 
			
		||||
  AxiosError,
 | 
			
		||||
  AxiosInstance,
 | 
			
		||||
  AxiosRequestConfig,
 | 
			
		||||
  AxiosResponse,
 | 
			
		||||
  InternalAxiosRequestConfig,
 | 
			
		||||
} from "axios";
 | 
			
		||||
import { message } from "ant-design-vue";
 | 
			
		||||
import { useUserStore } from "@/stores/modules/userStore";
 | 
			
		||||
import { CLIENT_TYPE } from "@/config/constant";
 | 
			
		||||
export interface JsonResult<T> {
 | 
			
		||||
  code: number;
 | 
			
		||||
  message: string;
 | 
			
		||||
  data?: T;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const axiosConfig: AxiosRequestConfig = {
 | 
			
		||||
  baseURL: import.meta.env.VITE_APP_BASE_API,
 | 
			
		||||
  timeout: import.meta.env.VITE_APP_ENV === "production" ? 10000 : 60000,
 | 
			
		||||
  timeoutErrorMessage: "请求超时......",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class RequestHttp {
 | 
			
		||||
  service: AxiosInstance;
 | 
			
		||||
 | 
			
		||||
  public constructor(config: AxiosRequestConfig) {
 | 
			
		||||
    this.service = axios.create(config);
 | 
			
		||||
    //1.添加请求拦截
 | 
			
		||||
    this.service.interceptors.request.use(
 | 
			
		||||
      (config: InternalAxiosRequestConfig) => {
 | 
			
		||||
        //默认带上用户token
 | 
			
		||||
        config.headers.set("token", useUserStore().userInfo?.tokenValue);
 | 
			
		||||
        config.headers.set("Client-Type", CLIENT_TYPE);
 | 
			
		||||
        return config;
 | 
			
		||||
      },
 | 
			
		||||
      (error: AxiosError): Promise<string> => {
 | 
			
		||||
        message.error(error.message).then((r) => {});
 | 
			
		||||
        return Promise.reject(error);
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
    //2.添加响应拦截
 | 
			
		||||
    this.service.interceptors.response.use(
 | 
			
		||||
      (response: AxiosResponse): Promise<any> => {
 | 
			
		||||
        const jsonResult: JsonResult<unknown> = response.data;
 | 
			
		||||
        if (jsonResult.code !== 200) {
 | 
			
		||||
          //todo 一些特定的错误需要重新登录 这里暂时没处理
 | 
			
		||||
          message.error(jsonResult.message).then((r) => {});
 | 
			
		||||
          return Promise.reject(jsonResult);
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.resolve(jsonResult);
 | 
			
		||||
      },
 | 
			
		||||
      (error: AxiosError): Promise<string> => {
 | 
			
		||||
        message.error(error.message).then((r) => {});
 | 
			
		||||
        return Promise.reject(error);
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   *  常用请求方法封装
 | 
			
		||||
   */
 | 
			
		||||
  get<T>(
 | 
			
		||||
    url: string,
 | 
			
		||||
    params?: object,
 | 
			
		||||
    _object: AxiosRequestConfig = {}
 | 
			
		||||
  ): Promise<JsonResult<T>> {
 | 
			
		||||
    return this.service.get(url, { params, ..._object });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  post<T>(
 | 
			
		||||
    url: string,
 | 
			
		||||
    params?: object | object[],
 | 
			
		||||
    _object: AxiosRequestConfig = {}
 | 
			
		||||
  ): Promise<JsonResult<T>> {
 | 
			
		||||
    return this.service.post(url, params, _object);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  put<T>(
 | 
			
		||||
    url: string,
 | 
			
		||||
    params?: object | object[],
 | 
			
		||||
    _object: AxiosRequestConfig = {}
 | 
			
		||||
  ): Promise<JsonResult<T>> {
 | 
			
		||||
    return this.service.put(url, params, _object);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  delete<T>(
 | 
			
		||||
    url: string,
 | 
			
		||||
    params?: object,
 | 
			
		||||
    _object: AxiosRequestConfig = {}
 | 
			
		||||
  ): Promise<JsonResult<T>> {
 | 
			
		||||
    return this.service.delete(url, { params, ..._object });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  download(
 | 
			
		||||
    url: string,
 | 
			
		||||
    params?: object,
 | 
			
		||||
    _object: AxiosRequestConfig = {}
 | 
			
		||||
  ): Promise<BlobPart> {
 | 
			
		||||
    return this.service.post(url, params, { ..._object, responseType: "blob" });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const api = new RequestHttp(axiosConfig);
 | 
			
		||||
export default api;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,42 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <svg :class="svgClass" aria-hidden="true">
 | 
			
		||||
    <use :xlink:href="iconClassName" :fill="color" />
 | 
			
		||||
  </svg>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { computed } from "vue";
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  iconName: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    required: true,
 | 
			
		||||
  },
 | 
			
		||||
  className: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: "",
 | 
			
		||||
  },
 | 
			
		||||
  color: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: "#ffffff",
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
// 图标在 iconfont 中的名字
 | 
			
		||||
const iconClassName = computed(() => {
 | 
			
		||||
  return `#${props.iconName}`;
 | 
			
		||||
});
 | 
			
		||||
// 给图标添加上类名
 | 
			
		||||
const svgClass = computed(() => {
 | 
			
		||||
  if (props.className) {
 | 
			
		||||
    return `svg-icon ${props.className}`;
 | 
			
		||||
  }
 | 
			
		||||
  return "svg-icon";
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
.svg-icon {
 | 
			
		||||
  width: 2em;
 | 
			
		||||
  height: 1.5em;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  fill: currentColor;
 | 
			
		||||
  vertical-align: -2px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,54 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="not-container">
 | 
			
		||||
    <img src="@/assets/images/404.png" class="not-img" alt="404" />
 | 
			
		||||
    <div class="not-detail">
 | 
			
		||||
      <h2>404</h2>
 | 
			
		||||
      <h4>抱歉,您访问的页面不存在~🤷♂️🤷♀️</h4>
 | 
			
		||||
      <!-- <a-button type="primary" @click="router.push(INDEX_URL)"
 | 
			
		||||
        >返回首页</a-button -->
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
.not-container {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
 | 
			
		||||
  .not-img {
 | 
			
		||||
    margin-right: 120px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .not-detail {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
 | 
			
		||||
    h2,
 | 
			
		||||
    h4 {
 | 
			
		||||
      padding: 0;
 | 
			
		||||
      margin: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    h2 {
 | 
			
		||||
      font-size: 60px;
 | 
			
		||||
      color: red;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    h4 {
 | 
			
		||||
      margin: 30px 0 20px;
 | 
			
		||||
      font-size: 19px;
 | 
			
		||||
      font-weight: normal;
 | 
			
		||||
      color: red;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
<script setup lang="ts"></script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div>500</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped></style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,153 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="layout-container">
 | 
			
		||||
    <div
 | 
			
		||||
      class="layout_left"
 | 
			
		||||
      :class="{ collapsed: useSetting.collapsed ? true : false }"
 | 
			
		||||
    >
 | 
			
		||||
      <Logo :collapsed="useSetting.collapsed"></Logo>
 | 
			
		||||
      <!-- 展示菜单 -->
 | 
			
		||||
      <div class="layout_left_item">
 | 
			
		||||
        <a-menu
 | 
			
		||||
          style="padding-right: 14px; width: 100%"
 | 
			
		||||
          theme="light"
 | 
			
		||||
          :inlineCollapsed="useSetting.collapsed ? true : false"
 | 
			
		||||
          mode="inline"
 | 
			
		||||
          v-model:openKeys="openKeys"
 | 
			
		||||
          v-model:selectedKeys="selectedKeys"
 | 
			
		||||
        >
 | 
			
		||||
          <!-- :selectedKeys="selectedKeys" -->
 | 
			
		||||
          <Menu-item :menu-item="userStore.menuRoutes"></Menu-item>
 | 
			
		||||
        </a-menu>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div
 | 
			
		||||
      class="layout_tabbar"
 | 
			
		||||
      :class="{ collapsed: useSetting.collapsed ? true : false }"
 | 
			
		||||
    >
 | 
			
		||||
      <tabbar v-model:collapsed="useSetting.collapsed"></tabbar>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div
 | 
			
		||||
      class="layout_content"
 | 
			
		||||
      :class="{ collapsed: useSetting.collapsed ? true : false }"
 | 
			
		||||
    >
 | 
			
		||||
      <div
 | 
			
		||||
        style="
 | 
			
		||||
          height: 40px;
 | 
			
		||||
          width: 100%;
 | 
			
		||||
          background-color: #fff;
 | 
			
		||||
          border-top: #ccc 1px solid;
 | 
			
		||||
        "
 | 
			
		||||
      >
 | 
			
		||||
        <tabsPane></tabsPane>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="layout_content_item">
 | 
			
		||||
        <router-view v-slot="{ Component, route }">
 | 
			
		||||
          <transition appear name="fade" mode="out-in">
 | 
			
		||||
            <keep-alive :include="keepAliveNames">
 | 
			
		||||
              <component v-if="flag" :is="Component" :key="route.fullPath" />
 | 
			
		||||
            </keep-alive>
 | 
			
		||||
          </transition>
 | 
			
		||||
        </router-view>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, computed, watch, nextTick } from "vue";
 | 
			
		||||
import Logo from "./logo/index.vue";
 | 
			
		||||
import MenuItem from "./menuItem/MenuItem.vue";
 | 
			
		||||
import tabbar from "./tabbar/index.vue";
 | 
			
		||||
import tabsPane from "@/components/tabsPane/index.vue";
 | 
			
		||||
import { useUserStore } from "@/stores/modules/userStore";
 | 
			
		||||
import { useUserSetting } from "@/stores/modules/setting";
 | 
			
		||||
import { useRoute } from "vue-router";
 | 
			
		||||
import api from "@/axios";
 | 
			
		||||
const userStore = useUserStore();
 | 
			
		||||
const useSetting = useUserSetting();
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
 | 
			
		||||
const keepAliveNames = ref<string[]>([]);
 | 
			
		||||
const openKeys = ref([useSetting.selectedKeys]);
 | 
			
		||||
 | 
			
		||||
const flag = ref(true);
 | 
			
		||||
// const resp = api.get("/management/auth/myAuthRouter");
 | 
			
		||||
// console.log(resp, "000");
 | 
			
		||||
 | 
			
		||||
// 全局刷新效果
 | 
			
		||||
watch(
 | 
			
		||||
  () => useSetting.refresh,
 | 
			
		||||
  () => {
 | 
			
		||||
    flag.value = false;
 | 
			
		||||
    nextTick(() => {
 | 
			
		||||
      flag.value = true;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const selectedKeys = computed(() => [route.path]);
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.layout-container {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100vh;
 | 
			
		||||
  // display: flex;
 | 
			
		||||
  .layout_left {
 | 
			
		||||
    width: 260px;
 | 
			
		||||
    height: 100vh;
 | 
			
		||||
    // background: #001529;
 | 
			
		||||
    color: #fff;
 | 
			
		||||
    transition: all 0.3s;
 | 
			
		||||
    .layout_left_item {
 | 
			
		||||
      height: calc(100vh - 50px);
 | 
			
		||||
      overflow-y: auto;
 | 
			
		||||
    }
 | 
			
		||||
    &.collapsed {
 | 
			
		||||
      width: 65px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .layout_tabbar {
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    left: 260px;
 | 
			
		||||
    width: calc(100% - 260px);
 | 
			
		||||
    height: 50px;
 | 
			
		||||
    transition: all 0.3s;
 | 
			
		||||
    background: #f5f5f5;
 | 
			
		||||
    &.collapsed {
 | 
			
		||||
      width: calc(100% - 65px);
 | 
			
		||||
      left: 65px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .layout_content {
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    width: calc(100% - 260px);
 | 
			
		||||
    height: calc(100vh - 50px);
 | 
			
		||||
    top: 50px;
 | 
			
		||||
    left: 260px;
 | 
			
		||||
    // padding: 20px;
 | 
			
		||||
    overflow: auto;
 | 
			
		||||
    transition: all 0.3s;
 | 
			
		||||
    background: #f5f5f5;
 | 
			
		||||
    &.collapsed {
 | 
			
		||||
      width: calc(100% - 65px);
 | 
			
		||||
      left: 65px;
 | 
			
		||||
    }
 | 
			
		||||
    .layout_content_item {
 | 
			
		||||
      padding: 20px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.fade-enter-from {
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
  transition: all 0.2s;
 | 
			
		||||
  transform: translateX(-30px);
 | 
			
		||||
}
 | 
			
		||||
// .fade-enter-active {
 | 
			
		||||
//   opacity: 0;
 | 
			
		||||
// }
 | 
			
		||||
.fade-leave-to {
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
  transition: all 0.2s;
 | 
			
		||||
  transform: translateX(30px);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,44 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="logo">
 | 
			
		||||
    <img src="@/assets/vue.svg" alt="xxxx后台系统" />
 | 
			
		||||
    <p
 | 
			
		||||
      style="margin-left: 20px"
 | 
			
		||||
      :class="{ logoItem: props.collapsed ? true : false }"
 | 
			
		||||
    >
 | 
			
		||||
      智慧食堂系统
 | 
			
		||||
    </p>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  collapsed: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    default: false,
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.logo {
 | 
			
		||||
  height: 50px;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  color: black;
 | 
			
		||||
  padding: 20px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
  border-bottom: 1px solid #ccc;
 | 
			
		||||
  transition: all 0.3s;
 | 
			
		||||
  img {
 | 
			
		||||
    width: 30px;
 | 
			
		||||
    height: 30px;
 | 
			
		||||
  }
 | 
			
		||||
  p {
 | 
			
		||||
    font-size: 18px;
 | 
			
		||||
    transition: all 1.2s;
 | 
			
		||||
  }
 | 
			
		||||
  .logoItem {
 | 
			
		||||
    color: #fff;
 | 
			
		||||
    transition: none;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,50 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <template v-for="(item, index) in menuItem" :key="index">
 | 
			
		||||
    <a-sub-menu v-if="item.children" :key="item.path">
 | 
			
		||||
      <template #icon>
 | 
			
		||||
        <SvgIcon :icon-name="item.icon"></SvgIcon>
 | 
			
		||||
      </template>
 | 
			
		||||
      <template #title>
 | 
			
		||||
        <span style="margin-left: 5px">{{ item.name }}</span>
 | 
			
		||||
      </template>
 | 
			
		||||
      <menu-item :menu-item="item.children" />
 | 
			
		||||
    </a-sub-menu>
 | 
			
		||||
    <a-menu-item
 | 
			
		||||
      v-else
 | 
			
		||||
      :key="item.path + ''"
 | 
			
		||||
      @click="routerMenuItem(item.path)"
 | 
			
		||||
    >
 | 
			
		||||
      <template #icon>
 | 
			
		||||
        <SvgIcon :icon-name="item.icon"></SvgIcon>
 | 
			
		||||
      </template>
 | 
			
		||||
      <span style="margin-left: 5px">{{ item.name }}</span>
 | 
			
		||||
    </a-menu-item>
 | 
			
		||||
  </template>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
import { SystemMenu } from "@/types/config/index";
 | 
			
		||||
import SvgIcon from "@/components/SvgIcon/index.vue";
 | 
			
		||||
import { useUserSetting } from "@/stores/modules/setting";
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const useSetting = useUserSetting();
 | 
			
		||||
withDefaults(
 | 
			
		||||
  defineProps<{
 | 
			
		||||
    menuItem?: SystemMenu[];
 | 
			
		||||
  }>(),
 | 
			
		||||
  {
 | 
			
		||||
    menuItem: (): SystemMenu[] => {
 | 
			
		||||
      return [];
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
const routerMenuItem = (path: string) => {
 | 
			
		||||
  router.push(path);
 | 
			
		||||
  useSetting.setSelectedKeys(path);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.MenuItem {
 | 
			
		||||
  padding-inline: 16px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,147 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="tabbar">
 | 
			
		||||
    <div class="tabbar-left">
 | 
			
		||||
      <menu-unfold-outlined
 | 
			
		||||
        class="trigger"
 | 
			
		||||
        @click="changeIcon"
 | 
			
		||||
        v-if="useSetting.collapsed"
 | 
			
		||||
      />
 | 
			
		||||
      <menu-fold-outlined class="trigger" v-else @click="changeIcon" />
 | 
			
		||||
      <!-- 左侧面包屑 -->
 | 
			
		||||
      <a-breadcrumb>
 | 
			
		||||
        <template #separator><span>></span></template>
 | 
			
		||||
        <a-breadcrumb-item
 | 
			
		||||
          v-for="(item, index) in breadcrumbList"
 | 
			
		||||
          :key="item.path"
 | 
			
		||||
          v-show="item.name"
 | 
			
		||||
        >
 | 
			
		||||
          {{ item.name }}</a-breadcrumb-item
 | 
			
		||||
        >
 | 
			
		||||
      </a-breadcrumb>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="tabbar-right">
 | 
			
		||||
      <a-button style="margin-right: 8px" shape="circle" @click="refreshButton"
 | 
			
		||||
        ><sync-outlined
 | 
			
		||||
      /></a-button>
 | 
			
		||||
      <a-button style="margin-right: 8px" shape="circle" @click="fullScreen"
 | 
			
		||||
        ><ExpandOutlined
 | 
			
		||||
      /></a-button>
 | 
			
		||||
      <a-button style="margin-right: 8px" shape="circle"
 | 
			
		||||
        ><SettingOutlined
 | 
			
		||||
      /></a-button>
 | 
			
		||||
      <a-avatar src="https://www.antdv.com/assets/logo.1ef800a8.svg" />
 | 
			
		||||
      <a-dropdown v-model:open="visible">
 | 
			
		||||
        <a class="ant-dropdown-link" @click.prevent>
 | 
			
		||||
          admin
 | 
			
		||||
          <DownOutlined />
 | 
			
		||||
        </a>
 | 
			
		||||
        <template #overlay>
 | 
			
		||||
          <a-menu @click="handleMenuClick">
 | 
			
		||||
            <a-menu-item key="1">退出</a-menu-item>
 | 
			
		||||
            <a-menu-item key="2">个人中心</a-menu-item>
 | 
			
		||||
            <a-menu-item key="3">修改密码</a-menu-item>
 | 
			
		||||
          </a-menu>
 | 
			
		||||
        </template>
 | 
			
		||||
      </a-dropdown>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref, computed } from "vue";
 | 
			
		||||
import {
 | 
			
		||||
  MenuFoldOutlined,
 | 
			
		||||
  MenuUnfoldOutlined,
 | 
			
		||||
  SyncOutlined,
 | 
			
		||||
  ExpandOutlined,
 | 
			
		||||
  SettingOutlined,
 | 
			
		||||
  DownOutlined,
 | 
			
		||||
} from "@ant-design/icons-vue";
 | 
			
		||||
import type { MenuProps } from "ant-design-vue";
 | 
			
		||||
import { useUserSetting } from "@/stores/modules/setting";
 | 
			
		||||
import { useRoute, useRouter } from "vue-router";
 | 
			
		||||
import { useUserStore } from "@/stores/modules/userStore";
 | 
			
		||||
import router from "@/router";
 | 
			
		||||
const userStore = useUserStore();
 | 
			
		||||
const useSetting = useUserSetting();
 | 
			
		||||
const $Route = useRoute();
 | 
			
		||||
const $Router = useRouter();
 | 
			
		||||
 | 
			
		||||
const visible = ref(false);
 | 
			
		||||
const handleMenuClick: MenuProps["onClick"] = (e) => {
 | 
			
		||||
  if (e.key === "1") {
 | 
			
		||||
    // 退出登录
 | 
			
		||||
    userStore.deleteToken();
 | 
			
		||||
    $Router.push({ path: "/login", query: { redirect: $Route.path } });
 | 
			
		||||
  } else if (e.key === "3") {
 | 
			
		||||
    visible.value = false;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
const changeIcon = () => {
 | 
			
		||||
  // 保存在pinia
 | 
			
		||||
  useSetting.toggleCollapsed();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 全局刷新
 | 
			
		||||
const refreshButton = () => {
 | 
			
		||||
  useSetting.refreshPage();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 全屏;
 | 
			
		||||
const fullScreen = () => {
 | 
			
		||||
  let full = document.fullscreenElement; //获取状态
 | 
			
		||||
  if (!full) {
 | 
			
		||||
    document.documentElement.requestFullscreen();
 | 
			
		||||
  } else {
 | 
			
		||||
    document.exitFullscreen();
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const breadcrumbList: any = computed(() => {
 | 
			
		||||
  return $Route.matched.map((item) => ({
 | 
			
		||||
    path: item.path,
 | 
			
		||||
    name: item.name,
 | 
			
		||||
    children: item.children || [],
 | 
			
		||||
  }));
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.tabbar {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  background-color: #fff;
 | 
			
		||||
 | 
			
		||||
  .tabbar-left {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    .trigger {
 | 
			
		||||
      font-size: 18px;
 | 
			
		||||
      padding: 0 24px;
 | 
			
		||||
      cursor: pointer;
 | 
			
		||||
      transition: color 0.3s;
 | 
			
		||||
      border-left: 1px solid #ccc;
 | 
			
		||||
    }
 | 
			
		||||
    .breadcrumbItem {
 | 
			
		||||
      color: rgba(0, 0, 0, 0.45);
 | 
			
		||||
      transition: color 0.2s;
 | 
			
		||||
      padding: 0 4px;
 | 
			
		||||
      border-radius: 4px;
 | 
			
		||||
      height: 22px;
 | 
			
		||||
      display: inline-block;
 | 
			
		||||
      margin-inline: -4px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .tabbar-right {
 | 
			
		||||
    padding: 0 24px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    .ant-dropdown-link {
 | 
			
		||||
      color: rgba(0, 0, 0, 0.45);
 | 
			
		||||
      margin-left: 8px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,96 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <a-tabs
 | 
			
		||||
    type="editable-card"
 | 
			
		||||
    v-model:activeKey="activeKey"
 | 
			
		||||
    @edit="handleTabEdit"
 | 
			
		||||
    @tabClick="handleTabClick"
 | 
			
		||||
    hide-add
 | 
			
		||||
  >
 | 
			
		||||
    <a-tab-pane
 | 
			
		||||
      v-for="tab in tabsList"
 | 
			
		||||
      :key="tab.key"
 | 
			
		||||
      :tab="tab.title"
 | 
			
		||||
      :closable="tab.closable"
 | 
			
		||||
    >
 | 
			
		||||
    </a-tab-pane>
 | 
			
		||||
  </a-tabs>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { ref, watch, onMounted } from "vue";
 | 
			
		||||
import { useRouter, useRoute } from "vue-router";
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
 | 
			
		||||
// 标签页列表
 | 
			
		||||
const tabsList = ref([
 | 
			
		||||
  {
 | 
			
		||||
    key: "/home",
 | 
			
		||||
    title: "首页",
 | 
			
		||||
    closable: false,
 | 
			
		||||
  },
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
// 当前激活的标签页
 | 
			
		||||
const activeKey = ref(route.path);
 | 
			
		||||
 | 
			
		||||
// 初始化处理
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  addTab(route);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 监听路由变化
 | 
			
		||||
watch(
 | 
			
		||||
  () => route.path,
 | 
			
		||||
  (newPath) => {
 | 
			
		||||
    activeKey.value = newPath;
 | 
			
		||||
    addTab(route);
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// 添加标签页
 | 
			
		||||
const addTab = (route) => {
 | 
			
		||||
  const exists = tabsList.value.some((tab) => tab.key === route.path);
 | 
			
		||||
  if (!exists) {
 | 
			
		||||
    tabsList.value.push({
 | 
			
		||||
      key: route.path,
 | 
			
		||||
      title: route.name || "新tab",
 | 
			
		||||
      closable: route.path !== "/home", //首页不能关闭
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 处理标签页点击
 | 
			
		||||
const handleTabClick = (key) => {
 | 
			
		||||
  router.push(key);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 处理标签页编辑(关闭)
 | 
			
		||||
const handleTabEdit = (targetKey, action) => {
 | 
			
		||||
  if (action === "remove") {
 | 
			
		||||
    removeTab(targetKey);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 移除标签页
 | 
			
		||||
const removeTab = (targetKey) => {
 | 
			
		||||
  if (targetKey === "/home") return; // 阻止关闭首页
 | 
			
		||||
 | 
			
		||||
  const tabs = tabsList.value;
 | 
			
		||||
  let newActiveKey = activeKey.value;
 | 
			
		||||
 | 
			
		||||
  if (targetKey === activeKey.value) {
 | 
			
		||||
    tabs.forEach((tab, index) => {
 | 
			
		||||
      if (tab.key === targetKey) {
 | 
			
		||||
        const nextTab = tabs[index + 1] || tabs[index - 1];
 | 
			
		||||
        newActiveKey = nextTab?.key || "/home";
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  tabsList.value = tabs.filter((tab) => tab.key !== targetKey);
 | 
			
		||||
  activeKey.value = newActiveKey;
 | 
			
		||||
  router.push(newActiveKey);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
/**
 | 
			
		||||
 * 当前客户端类型
 | 
			
		||||
 */
 | 
			
		||||
export const CLIENT_TYPE = "management";
 | 
			
		||||
 | 
			
		||||
export const INDEX_ROUTER = {
 | 
			
		||||
  path: "/index",
 | 
			
		||||
  name: "1905141507795456000",
 | 
			
		||||
  meta: {
 | 
			
		||||
    title: "首页",
 | 
			
		||||
    icon: "LineMdHomeMd",
 | 
			
		||||
    isKeepAlive: false,
 | 
			
		||||
    btnList: [],
 | 
			
		||||
    isFixed: true,
 | 
			
		||||
    isFull: false,
 | 
			
		||||
    remark: "",
 | 
			
		||||
  } as any,
 | 
			
		||||
};
 | 
			
		||||
export const LOGIN_ROUTER = {
 | 
			
		||||
  path: "/login",
 | 
			
		||||
  name: "1905141542192943104",
 | 
			
		||||
  meta: {
 | 
			
		||||
    title: "登录",
 | 
			
		||||
    btnList: [],
 | 
			
		||||
    icon: "",
 | 
			
		||||
    isFixed: false,
 | 
			
		||||
    isKeepAlive: false,
 | 
			
		||||
    isFull: false,
 | 
			
		||||
    remark: "",
 | 
			
		||||
  } as any,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const ROUTER_WHITE_LIST: string[] = [LOGIN_ROUTER.path];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,70 @@
 | 
			
		|||
import api from "@/axios";
 | 
			
		||||
import { isEmpty, isNull } from "lodash-es";
 | 
			
		||||
 | 
			
		||||
type EnumType =
 | 
			
		||||
  | "EnableStatus"
 | 
			
		||||
  | "AnnouncementType"
 | 
			
		||||
  | "FileType"
 | 
			
		||||
  | "IndustryType"
 | 
			
		||||
  | "IsEnable"
 | 
			
		||||
  | "IsOrNot"
 | 
			
		||||
  | "MenuType"
 | 
			
		||||
  | "ProjectStatus"
 | 
			
		||||
  | "Sex";
 | 
			
		||||
 | 
			
		||||
export const initEnums = () => {
 | 
			
		||||
  api
 | 
			
		||||
    .get<Record<EnumType, SelectNodeVo<any>[]>>("/common/enums")
 | 
			
		||||
    .then((resp) => {
 | 
			
		||||
      sessionStorage.setItem("enumsMap", JSON.stringify(resp.data));
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const enumSelectNodes = <T, E = any>(
 | 
			
		||||
  enumType: EnumType
 | 
			
		||||
): SelectNodeVo<T, E>[] =>
 | 
			
		||||
  JSON.parse(sessionStorage.getItem("enumsMap") as string)?.[enumType];
 | 
			
		||||
 | 
			
		||||
export const defaultAllEnumSelectNodes = <T, E = any>(
 | 
			
		||||
  enumType: EnumType
 | 
			
		||||
): SelectNodeVo<T, E>[] => {
 | 
			
		||||
  return [
 | 
			
		||||
    {
 | 
			
		||||
      value: null as T,
 | 
			
		||||
      label: "全部",
 | 
			
		||||
    },
 | 
			
		||||
    ...enumSelectNodes<T, E>(enumType),
 | 
			
		||||
  ];
 | 
			
		||||
};
 | 
			
		||||
export const getEnumByValue = <T, E = any>(
 | 
			
		||||
  enumType: EnumType,
 | 
			
		||||
  value: T
 | 
			
		||||
): null | SelectNodeVo<T, E> => {
 | 
			
		||||
  const enumsMap: Record<EnumType, SelectNodeVo<T, E>[]> = JSON.parse(
 | 
			
		||||
    sessionStorage.getItem("enumsMap") as string
 | 
			
		||||
  );
 | 
			
		||||
  if (isNull(enumsMap)) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
  const enumList = enumsMap[enumType];
 | 
			
		||||
  if (isEmpty(enumList)) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
  for (let i = 0; i < enumList.length; i++) {
 | 
			
		||||
    if (value === enumList[i].value) {
 | 
			
		||||
      return enumList[i];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return null;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getEnumLabelByValue = <T>(
 | 
			
		||||
  enumType: EnumType,
 | 
			
		||||
  value: T
 | 
			
		||||
): string => {
 | 
			
		||||
  const enums = getEnumByValue(enumType, value);
 | 
			
		||||
  if (isNull(enums)) {
 | 
			
		||||
    return "-";
 | 
			
		||||
  }
 | 
			
		||||
  return enums.label;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,48 @@
 | 
			
		|||
declare const __APP_ENV__: ImportMetaEnv;
 | 
			
		||||
 | 
			
		||||
interface Window {
 | 
			
		||||
  _AMapSecurityConfig: {
 | 
			
		||||
    securityJsCode: string;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface tokenInfo {
 | 
			
		||||
  isLogin: boolean;
 | 
			
		||||
  loginDevice: string;
 | 
			
		||||
  loginId: string;
 | 
			
		||||
  loginType: string;
 | 
			
		||||
  sessionTimeout: string;
 | 
			
		||||
  tag: string;
 | 
			
		||||
  tokenActiveTimeout: string;
 | 
			
		||||
  tokenName: string;
 | 
			
		||||
  tokenSessionTimeout: string;
 | 
			
		||||
  tokenTimeout: string;
 | 
			
		||||
  tokenValue: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class SelectNodeVo<T, E = Record<string, any>> {
 | 
			
		||||
  value: T;
 | 
			
		||||
  label: string;
 | 
			
		||||
  options?: SelectNodeVo<T>[];
 | 
			
		||||
  orderIndex?: number;
 | 
			
		||||
  disabled?: boolean;
 | 
			
		||||
  extData?: E;
 | 
			
		||||
}
 | 
			
		||||
interface RouterVo {
 | 
			
		||||
  /** id **/
 | 
			
		||||
  snowFlakeId: string;
 | 
			
		||||
  /** 父级id **/
 | 
			
		||||
  parentId: string;
 | 
			
		||||
  /** 子项 **/
 | 
			
		||||
  children: unsupported;
 | 
			
		||||
  /** 名称 **/
 | 
			
		||||
  name: string;
 | 
			
		||||
  /** 菜单类型 **/
 | 
			
		||||
  type: SelectNodeVo<"dir" | "menu" | "btn">;
 | 
			
		||||
  /** 路径 **/
 | 
			
		||||
  path: string;
 | 
			
		||||
  /** 重定向地址 **/
 | 
			
		||||
  redirect: string;
 | 
			
		||||
  /** 路由元数据 **/
 | 
			
		||||
  meta: RouterMetaVo;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
import { createApp } from "vue";
 | 
			
		||||
import App from "@/App.vue";
 | 
			
		||||
 | 
			
		||||
// vue Router
 | 
			
		||||
import router from "@/router";
 | 
			
		||||
// AntDesignVue
 | 
			
		||||
import Antd from "ant-design-vue";
 | 
			
		||||
// pinia store
 | 
			
		||||
import pinia from "@/stores";
 | 
			
		||||
 | 
			
		||||
// AntDesignVue样式
 | 
			
		||||
import "ant-design-vue/dist/reset.css";
 | 
			
		||||
//浏览器重置样式
 | 
			
		||||
import "@/assets/scss/reset.scss";
 | 
			
		||||
//公共样式
 | 
			
		||||
import "@/assets/scss/common.scss";
 | 
			
		||||
// 阿里巴巴普惠体
 | 
			
		||||
import "@/assets/scss/font.scss";
 | 
			
		||||
// icon图标
 | 
			
		||||
import SvgIcon from "@/components/SvgIcon/index.vue";
 | 
			
		||||
import "@/assets/iconfont/iconfont.js";
 | 
			
		||||
import "@/styles/index.scss";
 | 
			
		||||
import { initEnums } from "@/config/dict";
 | 
			
		||||
// initEnums();
 | 
			
		||||
const vueApp = createApp(App);
 | 
			
		||||
vueApp.component("SvgIcon", SvgIcon);
 | 
			
		||||
vueApp.use(router).use(Antd).use(pinia).mount("#app");
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,69 @@
 | 
			
		|||
import {
 | 
			
		||||
  createRouter,
 | 
			
		||||
  createWebHistory,
 | 
			
		||||
  createWebHashHistory,
 | 
			
		||||
} from "vue-router";
 | 
			
		||||
import { constantRoute, errorRoute } from "./routes";
 | 
			
		||||
import { useUserStore } from "@/stores/modules/userStore";
 | 
			
		||||
 | 
			
		||||
const router = createRouter({
 | 
			
		||||
  history: createWebHashHistory(),
 | 
			
		||||
  routes: [...constantRoute, ...errorRoute],
 | 
			
		||||
  strict: false,
 | 
			
		||||
  scrollBehavior: () => ({ left: 0, top: 0 }), //滚动行为
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 前置路由守卫
 | 
			
		||||
 */
 | 
			
		||||
router.beforeEach((to: any, from: any, next: any) => {
 | 
			
		||||
  //动态设置标题
 | 
			
		||||
  const title: string = "后台管理系统";
 | 
			
		||||
  document.title = to.meta.name ? `${to.meta.name} - ${title}` : title;
 | 
			
		||||
  //todo 鉴权操作...
 | 
			
		||||
  const userStore = useUserStore();
 | 
			
		||||
  if (userStore.token) {
 | 
			
		||||
    if (to.path === "/login") {
 | 
			
		||||
      next({ path: "/home" });
 | 
			
		||||
    } else {
 | 
			
		||||
      next();
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    if (to.path === "/login") {
 | 
			
		||||
      next();
 | 
			
		||||
    } else {
 | 
			
		||||
      next({ path: "/login", query: { redirect: to.path } });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // if (userStore.userInfo?.tokenValue) {
 | 
			
		||||
  //   if (to.path === "/login") {
 | 
			
		||||
  //     next({ path: "/home" });
 | 
			
		||||
  //   } else {
 | 
			
		||||
  //     next();
 | 
			
		||||
  //   }
 | 
			
		||||
  // } else {
 | 
			
		||||
  //   if (to.path === "/login") {
 | 
			
		||||
  //     next();
 | 
			
		||||
  //   } else {
 | 
			
		||||
  //     next({ path: "/login", query: { redirect: to.path } });
 | 
			
		||||
  //   }
 | 
			
		||||
  // }
 | 
			
		||||
  //放行
 | 
			
		||||
  return next();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 路由跳转错误
 | 
			
		||||
 */
 | 
			
		||||
router.onError((error: any) => {
 | 
			
		||||
  console.warn("路由错误", error.message);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 后置路由守卫
 | 
			
		||||
 * */
 | 
			
		||||
router.afterEach((to: any, from: any) => {
 | 
			
		||||
  //...
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default router;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,199 @@
 | 
			
		|||
export const constantRoute: any = [
 | 
			
		||||
  {
 | 
			
		||||
    path: "/home",
 | 
			
		||||
    name: "首页",
 | 
			
		||||
    component: () => import("@/views/home/index.vue"),
 | 
			
		||||
    title: "home",
 | 
			
		||||
    icon: "icon-shouye1",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: "/screen",
 | 
			
		||||
    name: "数据大屏",
 | 
			
		||||
    component: () => import("@/views/screen/index.vue"),
 | 
			
		||||
    title: "screen",
 | 
			
		||||
    icon: "icon-shujudaping2",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "userManagement",
 | 
			
		||||
    name: "系统管理",
 | 
			
		||||
    path: "/userManagement",
 | 
			
		||||
    icon: "icon-quanxianguanli",
 | 
			
		||||
    children: [
 | 
			
		||||
      {
 | 
			
		||||
        title: "bgManagement",
 | 
			
		||||
        name: "用户管理",
 | 
			
		||||
        path: "/userManagement/bgManagement",
 | 
			
		||||
        icon: "icon-yonghuguanli",
 | 
			
		||||
        component: () => import("@/views/userManagement/bgManagement/index.vue"),
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: "role",
 | 
			
		||||
        name: "角色管理",
 | 
			
		||||
        path: "/userManagement/role",
 | 
			
		||||
        icon: "icon-jiaoseguanli2",
 | 
			
		||||
        component: () => import("@/views/userManagement/role/index.vue"),
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: "permission",
 | 
			
		||||
        name: "菜单管理",
 | 
			
		||||
        path: "/userManagement/permission",
 | 
			
		||||
        icon: "icon-caidanguanli2",
 | 
			
		||||
        component: () => import("@/views/userManagement/permission/index.vue"),
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: "log",
 | 
			
		||||
        name: "日志管理",
 | 
			
		||||
        path: "/userManagement/log",
 | 
			
		||||
        icon: "icon-caidanguanli2",
 | 
			
		||||
        component: () => import("@/views/userManagement/log/index.vue"),
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "product",
 | 
			
		||||
    name: "菜谱管理",
 | 
			
		||||
    path: "/product",
 | 
			
		||||
    icon: "icon-shangpinguanli3",
 | 
			
		||||
    children: [
 | 
			
		||||
      {
 | 
			
		||||
        title: "food",
 | 
			
		||||
        name: "食材管理",
 | 
			
		||||
        path: "/product/food",
 | 
			
		||||
        icon: "icon-shuxingguanli2",
 | 
			
		||||
        component: () => import("@/views/product/food/index.vue"),
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: "dishes",
 | 
			
		||||
        name: "菜品管理",
 | 
			
		||||
        path: "/product/dishes",
 | 
			
		||||
        icon: "icon-shuxingguanli2",
 | 
			
		||||
        component: () => import("@/views/product/dishes/index.vue"),
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: "trademark",
 | 
			
		||||
        name: "菜单",
 | 
			
		||||
        path: "/product/trademark",
 | 
			
		||||
        icon: "icon-pinpaiguanli2",
 | 
			
		||||
        component: () => import("@/views/product/trademark/index.vue"),
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "foodprocurement",
 | 
			
		||||
    name: "食品采购管理",
 | 
			
		||||
    path: "/foodprocurement",
 | 
			
		||||
    icon: "icon-shangpinguanli3",
 | 
			
		||||
    children: [
 | 
			
		||||
      {
 | 
			
		||||
        title: "directory",
 | 
			
		||||
        name: "采购名录",
 | 
			
		||||
        path: "/foodprocurement/directory",
 | 
			
		||||
        icon: "icon-shuxingguanli2",
 | 
			
		||||
        component: () => import("@/views/foodprocurement/directory/index.vue"),
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: "plan",
 | 
			
		||||
        name: "采购计划",
 | 
			
		||||
        path: "/foodprocurement/plan",
 | 
			
		||||
        icon: "icon-shuxingguanli2",
 | 
			
		||||
        component: () => import("@/views/foodprocurement/plan/index.vue"),
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: "orders",
 | 
			
		||||
        name: "采购订单",
 | 
			
		||||
        path: "/foodprocurement/orders",
 | 
			
		||||
        icon: "icon-shuxingguanli2",
 | 
			
		||||
        component: () => import("@/views/foodprocurement/orders/index.vue"),
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: "inventory",
 | 
			
		||||
        name: "库存管理",
 | 
			
		||||
        path: "/foodprocurement/inventory",
 | 
			
		||||
        icon: "icon-shuxingguanli2",
 | 
			
		||||
        component: () => import("@/views/foodprocurement/inventory/index.vue"),
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: "dataStatistics",
 | 
			
		||||
        name: "采购数据统计",
 | 
			
		||||
        path: "/foodprocurement/dataStatistics",
 | 
			
		||||
        icon: "icon-shuxingguanli2",
 | 
			
		||||
        component: () => import("@/views/foodprocurement/dataStatistics/index.vue"),
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: "/foodSampleManagement",
 | 
			
		||||
    name: "食品留样管理",
 | 
			
		||||
    component: () => import("@/views/foodSampleManagement/index.vue"),
 | 
			
		||||
    title: "foodSampleManagement",
 | 
			
		||||
    icon: "icon-shujudaping2",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: "/workAccounts",
 | 
			
		||||
    name: "工作台账管理",
 | 
			
		||||
    component: () => import("@/views/workAccounts/index.vue"),
 | 
			
		||||
    title: "workAccounts",
 | 
			
		||||
    icon: "icon-shujudaping2",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: "/smart",
 | 
			
		||||
    name: "智能分析统计",
 | 
			
		||||
    component: () => import("@/views/smart/index.vue"),
 | 
			
		||||
    title: "smart",
 | 
			
		||||
    icon: "icon-shujudaping2",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: "/foodAafetyEducation",
 | 
			
		||||
    name: "食安教育",
 | 
			
		||||
    component: () => import("@/views/foodAafetyEducation/index.vue"),
 | 
			
		||||
    title: "foodAafetyEducation",
 | 
			
		||||
    icon: "icon-shujudaping2",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: "/hardwareData",
 | 
			
		||||
    name: "硬件数据集成",
 | 
			
		||||
    component: () => import("@/views/hardwareData/index.vue"),
 | 
			
		||||
    title: "hardwareData",
 | 
			
		||||
    icon: "icon-shujudaping2",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: "/earlyWarning",
 | 
			
		||||
    name: "智能预警中心",
 | 
			
		||||
    component: () => import("@/views/earlyWarning/index.vue"),
 | 
			
		||||
    title: "earlyWarning",
 | 
			
		||||
    icon: "icon-shujudaping2",
 | 
			
		||||
  }
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const errorRoute: any = [
 | 
			
		||||
  {
 | 
			
		||||
    path: "/login",
 | 
			
		||||
    name: "登录",
 | 
			
		||||
    component: () => import("@/views/login/login.vue"),
 | 
			
		||||
    title: "login",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: "/404",
 | 
			
		||||
    name: "404",
 | 
			
		||||
    component: () => import("@/components/errorMessage/404.vue"),
 | 
			
		||||
    title: "404",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: "/",
 | 
			
		||||
    name: "",
 | 
			
		||||
    title: "",
 | 
			
		||||
    component: () => import("@/components/layout/index.vue"),
 | 
			
		||||
    children: constantRoute,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: "/500",
 | 
			
		||||
    name: "500",
 | 
			
		||||
    title: "500",
 | 
			
		||||
    component: () => import("@/components/errorMessage/500.vue"),
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: "/:pathMatch(.*)*",
 | 
			
		||||
    redirect: "/404",
 | 
			
		||||
    component: () => import("@/components/errorMessage/404.vue"),
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
import { createPinia } from "pinia";
 | 
			
		||||
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
 | 
			
		||||
 | 
			
		||||
//创建store实例 持久化插件
 | 
			
		||||
const pinia = createPinia();
 | 
			
		||||
pinia.use(piniaPluginPersistedstate);
 | 
			
		||||
 | 
			
		||||
export default pinia;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
// 小仓库:动态路由
 | 
			
		||||
import { defineStore } from "pinia";
 | 
			
		||||
import { ref } from "vue";
 | 
			
		||||
import api from "@/axios";
 | 
			
		||||
export const routingStore = defineStore("routing", () => {
 | 
			
		||||
  // const resp = api.get("/management/auth/myAuthRouter");
 | 
			
		||||
  // console.log(resp);
 | 
			
		||||
 | 
			
		||||
  return {};
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
// 小仓库:layout组件的配置
 | 
			
		||||
import { defineStore } from "pinia";
 | 
			
		||||
export const useUserSetting = defineStore({
 | 
			
		||||
  id: "user-setting",
 | 
			
		||||
  state: () => {
 | 
			
		||||
    return {
 | 
			
		||||
      collapsed: false, //侧边栏折叠
 | 
			
		||||
      selectedKeys: "",
 | 
			
		||||
      refresh: false, //全局刷新
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  actions: {
 | 
			
		||||
    toggleCollapsed() {
 | 
			
		||||
      this.collapsed = !this.collapsed;
 | 
			
		||||
    },
 | 
			
		||||
    refreshPage() {
 | 
			
		||||
      this.refresh = !this.refresh;
 | 
			
		||||
    },
 | 
			
		||||
    setSelectedKeys(key: string) {
 | 
			
		||||
      this.selectedKeys = key;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  getters: {},
 | 
			
		||||
  persist: {},
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,41 @@
 | 
			
		|||
import { defineStore } from "pinia";
 | 
			
		||||
 | 
			
		||||
import { constantRoute, errorRoute } from "@/router/routes";
 | 
			
		||||
import { SystemMenu } from "@/types/config/index";
 | 
			
		||||
 | 
			
		||||
export interface UserStore {
 | 
			
		||||
  userInfo: tokenInfo | undefined;
 | 
			
		||||
  menuRoutes: SystemMenu[];
 | 
			
		||||
  token:string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const useUserStore = defineStore({
 | 
			
		||||
  id: "user-stores",
 | 
			
		||||
  state: (): UserStore => {
 | 
			
		||||
    return {
 | 
			
		||||
      userInfo: undefined,
 | 
			
		||||
      menuRoutes: constantRoute,
 | 
			
		||||
      token:''
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  actions: {
 | 
			
		||||
    tokenList(token:string){
 | 
			
		||||
      this.token = token
 | 
			
		||||
    },
 | 
			
		||||
    setTokenInfo(user: tokenInfo) {
 | 
			
		||||
      // 保存userinfo
 | 
			
		||||
      this.userInfo = user;
 | 
			
		||||
    },
 | 
			
		||||
    // 清空用户信息(退出登录)
 | 
			
		||||
    deleteToken() {
 | 
			
		||||
      this.token = undefined
 | 
			
		||||
      this.userInfo = undefined;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  persist: {
 | 
			
		||||
    key: "user-stores",
 | 
			
		||||
    storage: window.localStorage,
 | 
			
		||||
    paths:["token"]
 | 
			
		||||
    // paths: ["userInfo"],
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
@import "./variable.scss";
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
// 项目全局样式
 | 
			
		||||
$base-color: red;
 | 
			
		||||
 | 
			
		||||
// 左侧菜单颜色
 | 
			
		||||
$base-menu-width: 260px;
 | 
			
		||||
 | 
			
		||||
// 左侧菜单的背景颜色
 | 
			
		||||
$base-menu-background: #001529;
 | 
			
		||||
// 顶部导航的高度
 | 
			
		||||
$base-tabbar-height: 60px;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
import { RouteComponent } from "vue-router";
 | 
			
		||||
 | 
			
		||||
export interface SystemMenu {
 | 
			
		||||
  hidden?: boolean;
 | 
			
		||||
  title?: string;
 | 
			
		||||
  path?: string;
 | 
			
		||||
  name?: string;
 | 
			
		||||
  icon?: string;
 | 
			
		||||
  component?: RouteComponent;
 | 
			
		||||
  children?: SystemMenu[];
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
import { JSEncrypt } from "jsencrypt";
 | 
			
		||||
 | 
			
		||||
const rsa = new JSEncrypt();
 | 
			
		||||
// rsa.setPublicKey(__APP_ENV.VITE_APP_JS_ENCRYPT_PUBLIC_KEY)
 | 
			
		||||
rsa.setPublicKey(import.meta.env.VITE_APP_CRYPTO_JS_SECRET_KEY);
 | 
			
		||||
 | 
			
		||||
export const encryptStr = (text: string): string => {
 | 
			
		||||
  const r = rsa.encrypt(text);
 | 
			
		||||
  if (!r) {
 | 
			
		||||
    throw "加密失败";
 | 
			
		||||
  }
 | 
			
		||||
  return r;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  encryptStr,
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div>智能预警中心</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div>食安教育</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div>食品留样管理</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div>采购数据统计</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
   <div>采购名录</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div>库存管理</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div>采购订单</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div>采购计划</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div>硬件数据集成</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,160 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="home">
 | 
			
		||||
    <a-card :bordered="false" style="width: 100%">
 | 
			
		||||
      <div class="homeIndex">
 | 
			
		||||
        <div>
 | 
			
		||||
          <a-form name="horizontal_login" layout="inline" autocomplete="off">
 | 
			
		||||
            <a-form-item label="名字" name="username">
 | 
			
		||||
              <a-input placeholder="请输入名字"> </a-input>
 | 
			
		||||
            </a-form-item>
 | 
			
		||||
            <a-form-item label="年龄" name="username">
 | 
			
		||||
              <a-input placeholder="请输入名字"> </a-input>
 | 
			
		||||
            </a-form-item>
 | 
			
		||||
            <a-form-item label="地址" name="username">
 | 
			
		||||
              <a-input placeholder="请输入名字"> </a-input>
 | 
			
		||||
            </a-form-item>
 | 
			
		||||
            <a-form-item label="时间" name="range-picker">
 | 
			
		||||
              <a-range-picker value-format="YYYY-MM-DD" />
 | 
			
		||||
            </a-form-item>
 | 
			
		||||
            <a-form-item label="名字" name="username">
 | 
			
		||||
              <a-input placeholder="请输入名字"> </a-input>
 | 
			
		||||
            </a-form-item>
 | 
			
		||||
          </a-form>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
          <a-button type="primary">
 | 
			
		||||
            <template #icon>
 | 
			
		||||
              <ZoomInOutlined />
 | 
			
		||||
            </template>
 | 
			
		||||
            搜索</a-button
 | 
			
		||||
          >
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </a-card>
 | 
			
		||||
    <a-card :bordered="false" class="homeItem">
 | 
			
		||||
      <div>
 | 
			
		||||
        <a-button type="primary" style="margin-right: 10px">
 | 
			
		||||
          <template #icon>
 | 
			
		||||
            <PlusOutlined />
 | 
			
		||||
          </template>
 | 
			
		||||
          新增</a-button
 | 
			
		||||
        >
 | 
			
		||||
        <a-button
 | 
			
		||||
          type="primary"
 | 
			
		||||
          :disabled="!hasSelected"
 | 
			
		||||
          :loading="state.loading"
 | 
			
		||||
          @click="start"
 | 
			
		||||
        >
 | 
			
		||||
          Reload {{ state.selectedRowKeys.length }}
 | 
			
		||||
        </a-button>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div style="margin-bottom: 16px"></div>
 | 
			
		||||
      <a-table
 | 
			
		||||
        :row-selection="{
 | 
			
		||||
          selectedRowKeys: state.selectedRowKeys,
 | 
			
		||||
          onChange: onSelectChange,
 | 
			
		||||
        }"
 | 
			
		||||
        :columns="columns"
 | 
			
		||||
        :data-source="data"
 | 
			
		||||
        :scroll="{ x: 1500, y: 430 }"
 | 
			
		||||
      />
 | 
			
		||||
    </a-card>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="tsx" setup>
 | 
			
		||||
import { PlusOutlined, ZoomInOutlined } from "@ant-design/icons-vue";
 | 
			
		||||
import { computed, reactive } from "vue";
 | 
			
		||||
 | 
			
		||||
type Key = string | number;
 | 
			
		||||
 | 
			
		||||
interface DataType {
 | 
			
		||||
  key: Key;
 | 
			
		||||
  name: string;
 | 
			
		||||
  age: number;
 | 
			
		||||
  address: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const columns = [
 | 
			
		||||
  {
 | 
			
		||||
    title: "名字",
 | 
			
		||||
    dataIndex: "name",
 | 
			
		||||
    customRender: (text: any) => {
 | 
			
		||||
      return text.value;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "年龄",
 | 
			
		||||
    dataIndex: "age",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "地址",
 | 
			
		||||
    dataIndex: "address",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: " 操作",
 | 
			
		||||
    dataIndex: "operation",
 | 
			
		||||
    customRender: () => {
 | 
			
		||||
      return (
 | 
			
		||||
        <div>
 | 
			
		||||
          <a-button type="primary" danger class="margin-right-sm">
 | 
			
		||||
            删除
 | 
			
		||||
          </a-button>
 | 
			
		||||
          <a-button class="margin-right-sm">查看</a-button>
 | 
			
		||||
          <a-button type="primary">编辑</a-button>
 | 
			
		||||
        </div>
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const data: DataType[] = [];
 | 
			
		||||
for (let i = 0; i < 100; i++) {
 | 
			
		||||
  data.push({
 | 
			
		||||
    key: i,
 | 
			
		||||
    name: `小明 ${i}`,
 | 
			
		||||
    age: 32 + i,
 | 
			
		||||
    address: `湖南省长沙市岳麓区. ${i}`,
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const state = reactive<{
 | 
			
		||||
  selectedRowKeys: Key[];
 | 
			
		||||
  loading: boolean;
 | 
			
		||||
}>({
 | 
			
		||||
  selectedRowKeys: [], //单击此处配置默认列
 | 
			
		||||
  loading: false,
 | 
			
		||||
});
 | 
			
		||||
const hasSelected = computed(() => state.selectedRowKeys.length > 0);
 | 
			
		||||
 | 
			
		||||
const start = () => {
 | 
			
		||||
  state.loading = true;
 | 
			
		||||
  // ajax request after empty completing
 | 
			
		||||
  setTimeout(() => {
 | 
			
		||||
    state.loading = false;
 | 
			
		||||
    state.selectedRowKeys = [];
 | 
			
		||||
  }, 1000);
 | 
			
		||||
};
 | 
			
		||||
const onSelectChange = (selectedRowKeys: Key[]) => {
 | 
			
		||||
  console.log("selectedRowKeys changed: ", selectedRowKeys);
 | 
			
		||||
  state.selectedRowKeys = selectedRowKeys;
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.home {
 | 
			
		||||
  height: calc(100vh - 130px);
 | 
			
		||||
  // background: #fff;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  .homeIndex {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
  }
 | 
			
		||||
  .homeItem {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: calc(100vh - 100px);
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    margin-top: 20px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,398 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div id="container">
 | 
			
		||||
    <div class="checkbox" style="z-index: 999999">
 | 
			
		||||
      <div class="checkboxItem">
 | 
			
		||||
        <a-checkbox
 | 
			
		||||
          style="margin-right: 10px"
 | 
			
		||||
          v-model:checked="state.checkAll"
 | 
			
		||||
          :indeterminate="state.indeterminate"
 | 
			
		||||
          @change="onCheckAllChange"
 | 
			
		||||
        >
 | 
			
		||||
          全部
 | 
			
		||||
        </a-checkbox>
 | 
			
		||||
        <a-checkbox-group
 | 
			
		||||
          v-model:value="state.checkedList"
 | 
			
		||||
          :options="plainOptions"
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="tsx">
 | 
			
		||||
import { onMounted, onUnmounted, reactive, ref, watch } from "vue";
 | 
			
		||||
import AMapLoader from "@amap/amap-jsapi-loader";
 | 
			
		||||
import IconAssets from "@/assets/images/point.png";
 | 
			
		||||
let maps: any = null;
 | 
			
		||||
let loadedAMap: any;
 | 
			
		||||
let cluster: any = null;
 | 
			
		||||
let deviceInfoWindow = ref<any>();
 | 
			
		||||
const wristbandIcon = ref<any>();
 | 
			
		||||
 | 
			
		||||
const plainOptions = [
 | 
			
		||||
  "保安",
 | 
			
		||||
  "警察",
 | 
			
		||||
  "移动设备",
 | 
			
		||||
  "手持设备",
 | 
			
		||||
  "AI设备",
 | 
			
		||||
  "固定设备",
 | 
			
		||||
];
 | 
			
		||||
const state = reactive({
 | 
			
		||||
  indeterminate: false,
 | 
			
		||||
  checkAll: true,
 | 
			
		||||
  checkedList: plainOptions,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
var points = [
 | 
			
		||||
  {
 | 
			
		||||
    weight: 8,
 | 
			
		||||
    lnglat: ["116.506647", "39.795337"],
 | 
			
		||||
    name: "北京",
 | 
			
		||||
    id: 0,
 | 
			
		||||
    age: 15,
 | 
			
		||||
    phone: "13575452134",
 | 
			
		||||
    address: "长沙市岳麓区麓谷街道芜湖",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    weight: 1,
 | 
			
		||||
    lnglat: ["116.843352", "40.377362"],
 | 
			
		||||
    name: "上海",
 | 
			
		||||
    id: 1,
 | 
			
		||||
    age: 18,
 | 
			
		||||
    phone: "13575452134",
 | 
			
		||||
    address: "长沙市开福区麓谷街道芜湖",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    weight: 1,
 | 
			
		||||
    lnglat: ["116.637122", "40.324272"],
 | 
			
		||||
    name: "成都",
 | 
			
		||||
    id: 2,
 | 
			
		||||
    age: 25,
 | 
			
		||||
    phone: "13575452134",
 | 
			
		||||
    address: "长沙市雨花区麓谷街道芜湖",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    weight: 1,
 | 
			
		||||
    lnglat: ["116.105381", "39.937183"],
 | 
			
		||||
    name: "天津",
 | 
			
		||||
    id: 3,
 | 
			
		||||
    age: 45,
 | 
			
		||||
    phone: "13575452134",
 | 
			
		||||
    address: "衡阳市石鼓区",
 | 
			
		||||
    units: "衡阳市石鼓区牛逼有限公司",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    weight: 1,
 | 
			
		||||
    lnglat: ["116.653525", "40.128936"],
 | 
			
		||||
    name: "南京",
 | 
			
		||||
    id: 4,
 | 
			
		||||
    age: 45,
 | 
			
		||||
    phone: "13575452134",
 | 
			
		||||
    address: "岳阳市镜湖区",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    weight: 1,
 | 
			
		||||
    lnglat: ["116.486409", "39.921489"],
 | 
			
		||||
    name: "长沙",
 | 
			
		||||
    id: 5,
 | 
			
		||||
    age: 65,
 | 
			
		||||
    phone: "13575452134",
 | 
			
		||||
    address: "长沙市岳麓区麓谷街道芜湖",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    weight: 1,
 | 
			
		||||
    lnglat: ["116.658603", "39.902486"],
 | 
			
		||||
    name: "衡阳",
 | 
			
		||||
    id: 6,
 | 
			
		||||
    age: 55,
 | 
			
		||||
    phone: "13575452134",
 | 
			
		||||
    address: "长沙市岳麓区麓谷街道芜湖",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    weight: 1,
 | 
			
		||||
    lnglat: ["116.338033", "39.728908"],
 | 
			
		||||
    name: "北京1",
 | 
			
		||||
    id: 7,
 | 
			
		||||
    age: 35,
 | 
			
		||||
    phone: "13575452134",
 | 
			
		||||
    address: "长沙市岳麓区麓谷街道芜湖",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    weight: 1,
 | 
			
		||||
    lnglat: ["116.235906", "40.218085"],
 | 
			
		||||
    name: "北京2",
 | 
			
		||||
    id: 8,
 | 
			
		||||
    age: 65,
 | 
			
		||||
    phone: "13575452134",
 | 
			
		||||
    address: "长沙市岳麓区麓谷街道芜湖",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    weight: 1,
 | 
			
		||||
    lnglat: ["116.366794", "39.915309"],
 | 
			
		||||
    name: "北京3",
 | 
			
		||||
    id: 9,
 | 
			
		||||
    age: 12,
 | 
			
		||||
    phone: "13575452134",
 | 
			
		||||
    address: "长沙市岳麓区麓谷街道芜湖",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    weight: 1,
 | 
			
		||||
    lnglat: ["116.418757", "39.917544"],
 | 
			
		||||
    name: "北京4",
 | 
			
		||||
    id: 10,
 | 
			
		||||
    age: 12,
 | 
			
		||||
    phone: "13575452134",
 | 
			
		||||
    address: "长沙市岳麓区麓谷街道芜湖",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    weight: 1,
 | 
			
		||||
    lnglat: ["116.139157", "39.735535"],
 | 
			
		||||
    name: "北京5",
 | 
			
		||||
    id: 11,
 | 
			
		||||
    age: 12,
 | 
			
		||||
    phone: "13575452134",
 | 
			
		||||
    address: "长沙市岳麓区麓谷街道芜湖",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    weight: 1,
 | 
			
		||||
    lnglat: ["116.195445", "39.914601"],
 | 
			
		||||
    name: "北京6",
 | 
			
		||||
    id: 12,
 | 
			
		||||
    age: 12,
 | 
			
		||||
    phone: "13575452134",
 | 
			
		||||
    address: "长沙市岳麓区麓谷街道芜湖",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    weight: 1,
 | 
			
		||||
    lnglat: ["116.310316", "39.956074"],
 | 
			
		||||
    name: "北京7",
 | 
			
		||||
    id: 13,
 | 
			
		||||
    age: 12,
 | 
			
		||||
    phone: "13575452134",
 | 
			
		||||
    address: "长沙市岳麓区麓谷街道芜湖",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    weight: 1,
 | 
			
		||||
    lnglat: ["116.286968", "39.863642"],
 | 
			
		||||
    name: "北京8",
 | 
			
		||||
    id: 14,
 | 
			
		||||
    age: 12,
 | 
			
		||||
    phone: "13575452134",
 | 
			
		||||
    address: "长沙市岳麓区麓谷街道芜湖",
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const count = points.length;
 | 
			
		||||
window._AMapSecurityConfig = {
 | 
			
		||||
  securityJsCode: "35be6bbf6cd521a1f17a9e455e52ffe9",
 | 
			
		||||
};
 | 
			
		||||
const initMap = () => {
 | 
			
		||||
  AMapLoader.load({
 | 
			
		||||
    key: "b886c3f67152803e081f0b0f5594a55e",
 | 
			
		||||
    version: "2.0",
 | 
			
		||||
    plugins: ["AMap.MarkerCluster"],
 | 
			
		||||
  }).then((AMap) => {
 | 
			
		||||
    loadedAMap = AMap;
 | 
			
		||||
    maps = new AMap.Map("container", {
 | 
			
		||||
      // 设置地图容器id
 | 
			
		||||
      resizeEnable: true,
 | 
			
		||||
      viewMode: "3D", // 是否为3D地图模式
 | 
			
		||||
      zoom: 10, // 初始化地图级别
 | 
			
		||||
      center: [116.407387, 39.904179], // 初始化地图中心点位置
 | 
			
		||||
    });
 | 
			
		||||
    wristbandIcon.value = new AMap.Icon({
 | 
			
		||||
      image: IconAssets, //Icon 的图像
 | 
			
		||||
      imageOffset: new AMap.Pixel(-9, -3), //图像相对展示区域的偏移量,适于雪碧图等
 | 
			
		||||
      imageSize: new AMap.Size(40, 40), //根据所设置的大小拉伸或压缩图片
 | 
			
		||||
    });
 | 
			
		||||
    createCluster();
 | 
			
		||||
    mapZoom();
 | 
			
		||||
    console.log(1111);
 | 
			
		||||
    maps.on("zoomend", function () {
 | 
			
		||||
      clearMarkers();
 | 
			
		||||
      let zoom = maps.getZoom();
 | 
			
		||||
      if (zoom > 10) {
 | 
			
		||||
        addNewMarkers();
 | 
			
		||||
        cluster?.setMap(null); // 清除聚合点,避免默认聚合样式覆盖
 | 
			
		||||
      } else {
 | 
			
		||||
        createCluster(); // 缩小时重新创建点聚合
 | 
			
		||||
        mapZoom();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const addNewMarkers = () => {
 | 
			
		||||
  points.forEach((item: any) => {
 | 
			
		||||
    const marker = new loadedAMap.Marker({
 | 
			
		||||
      position: item.lnglat,
 | 
			
		||||
      icon: new loadedAMap.Icon({
 | 
			
		||||
        image: IconAssets,
 | 
			
		||||
        size: new loadedAMap.Size(40, 46.5),
 | 
			
		||||
        imageSize: new loadedAMap.Size(30, 30),
 | 
			
		||||
      }),
 | 
			
		||||
    });
 | 
			
		||||
    marker.on("click", function () {
 | 
			
		||||
      deviceInfoWindow.value = new loadedAMap.InfoWindow({
 | 
			
		||||
        isCustom: true,
 | 
			
		||||
        content: content.value,
 | 
			
		||||
        offset: new loadedAMap.Pixel(5, -13),
 | 
			
		||||
      });
 | 
			
		||||
      deviceInfoWindow.value.open(maps, item.lnglat);
 | 
			
		||||
    });
 | 
			
		||||
    contents(item);
 | 
			
		||||
    maps.add(marker);
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const content = ref();
 | 
			
		||||
const contents = (item: any) => {
 | 
			
		||||
  content.value = `<div class="comments" style="width: 300px;">
 | 
			
		||||
                <ul class="commentUl" style="list-style: none">
 | 
			
		||||
                   <li><h2 style="color: skyblue">${item.name}</h2></li>
 | 
			
		||||
                   <li>年龄:<span>${item.age}</span></li>
 | 
			
		||||
                   <li>电话:<span>${item.phone}</span></li>
 | 
			
		||||
                   <li>地址:<span>${item.address}</span></li>
 | 
			
		||||
                </ul>
 | 
			
		||||
                 </div>`;
 | 
			
		||||
};
 | 
			
		||||
const mapZoom = () => {
 | 
			
		||||
  cluster.on("click", (e: any) => {
 | 
			
		||||
    const { clusterData, lnglat } = e;
 | 
			
		||||
    let currentZoom = maps.getZoom();
 | 
			
		||||
    // 若当前缩放层级 <= 12,点击聚合点时放大到更高层级以展开
 | 
			
		||||
    if (currentZoom < 10) {
 | 
			
		||||
      maps.setZoomAndCenter(currentZoom + 1, lnglat); // 放大2级,聚焦到点击位置
 | 
			
		||||
    }
 | 
			
		||||
    clearMarkers(); // 先清除地图上已有的 Marker,保留点聚合
 | 
			
		||||
    let markers: any[] = [];
 | 
			
		||||
    clusterData.forEach((item: any) => {
 | 
			
		||||
      const marker = new loadedAMap.Marker({
 | 
			
		||||
        position: item.lnglat,
 | 
			
		||||
        offset: new loadedAMap.Pixel(-15, -15),
 | 
			
		||||
        icon: new loadedAMap.Icon({
 | 
			
		||||
          image: IconAssets,
 | 
			
		||||
          size: new loadedAMap.Size(40, 46.5),
 | 
			
		||||
          imageSize: new loadedAMap.Size(30, 30),
 | 
			
		||||
        }),
 | 
			
		||||
      });
 | 
			
		||||
      marker.on("click", function () {
 | 
			
		||||
        deviceInfoWindow.value = new loadedAMap.InfoWindow({
 | 
			
		||||
          isCustom: true,
 | 
			
		||||
          content: content.value,
 | 
			
		||||
          offset: new loadedAMap.Pixel(5, -13),
 | 
			
		||||
        });
 | 
			
		||||
        deviceInfoWindow.value.open(maps, item.lnglat);
 | 
			
		||||
      });
 | 
			
		||||
      markers.push(marker);
 | 
			
		||||
      maps.add(marker);
 | 
			
		||||
    });
 | 
			
		||||
    // 自动调整视角,保证所有 Marker 可见
 | 
			
		||||
    maps.setFitView(markers);
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const createCluster = () => {
 | 
			
		||||
  if (cluster) cluster.setMap(null); // 避免重复创建
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  cluster = new loadedAMap.MarkerCluster(maps, points, {
 | 
			
		||||
    gridSize: 80,
 | 
			
		||||
    maxZoom: 12,
 | 
			
		||||
    renderClusterMarker: _renderClusterMarker,
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
// 清除 Marker 函数(避免重复)
 | 
			
		||||
function clearMarkers() {
 | 
			
		||||
  const overlays = maps.getAllOverlays("marker");
 | 
			
		||||
  overlays.forEach((marker: any) => {
 | 
			
		||||
    if (!marker.isCluster) {
 | 
			
		||||
      maps.remove(marker); // 仅移除非聚合 Marker
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const _renderClusterMarker = function (context: any) {
 | 
			
		||||
  var factor = Math.pow(context.count / count, 1 / 18);
 | 
			
		||||
  var div = document.createElement("div");
 | 
			
		||||
  var Hue = 180 - factor * 180;
 | 
			
		||||
  var bgColor = "hsla(" + Hue + ",100%,40%,0.7)";
 | 
			
		||||
  var fontColor = "hsla(" + Hue + ",100%,90%,1)";
 | 
			
		||||
  var borderColor = "hsla(" + Hue + ",100%,40%,1)";
 | 
			
		||||
  var shadowColor = "hsla(" + Hue + ",100%,90%,1)";
 | 
			
		||||
  div.style.backgroundColor = bgColor;
 | 
			
		||||
  var size = Math.round(30 + Math.pow(context.count / count, 1 / 5) * 20);
 | 
			
		||||
  div.style.width = div.style.height = size + "px";
 | 
			
		||||
  div.style.border = "solid 1px " + borderColor;
 | 
			
		||||
  div.style.borderRadius = size / 2 + "px";
 | 
			
		||||
  div.style.boxShadow = "0 0 5px " + shadowColor;
 | 
			
		||||
  div.innerHTML = context.count;
 | 
			
		||||
  div.style.lineHeight = size + "px";
 | 
			
		||||
  div.style.color = fontColor;
 | 
			
		||||
  div.style.fontSize = "14px";
 | 
			
		||||
  div.style.textAlign = "center";
 | 
			
		||||
  context.marker.setOffset(new loadedAMap.Pixel(-size / 2, -size / 2));
 | 
			
		||||
  context.marker.setContent(div);
 | 
			
		||||
};
 | 
			
		||||
// const _renderMarker = (context: any) => {
 | 
			
		||||
//   const content =
 | 
			
		||||
//     '<div style="background-color: hsla(180, 100%, 50%, 0.3); height: 18px; width: 18px; border: 1px solid hsl(180, 100%, 40%); border-radius: 12px; box-shadow: hsl(180, 100%, 50%) 0px 0px 3px;"></div>';
 | 
			
		||||
//   const offset = new loadedAMap.Pixel(-9, -9);
 | 
			
		||||
//   context.marker.setContent(content);
 | 
			
		||||
//   context.marker.setOffset(offset);
 | 
			
		||||
//   context.on("click", () => {
 | 
			
		||||
//     console.log(121);
 | 
			
		||||
//   });
 | 
			
		||||
// };
 | 
			
		||||
 | 
			
		||||
const onCheckAllChange = (e: any) => {
 | 
			
		||||
  console.log(e);
 | 
			
		||||
  Object.assign(state, {
 | 
			
		||||
    checkedList: e.target.checked ? plainOptions : [],
 | 
			
		||||
    indeterminate: true,
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
watch(
 | 
			
		||||
  () => state.checkedList,
 | 
			
		||||
  (val) => {
 | 
			
		||||
    console.log(val);
 | 
			
		||||
    if (val[0] !== plainOptions[0]) {
 | 
			
		||||
      clearMarkers();
 | 
			
		||||
    } else {
 | 
			
		||||
      addNewMarkers();
 | 
			
		||||
    }
 | 
			
		||||
    state.indeterminate = !!val.length && val.length < plainOptions.length;
 | 
			
		||||
    state.checkAll = val.length === plainOptions.length;
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  initMap();
 | 
			
		||||
});
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
  maps?.destroy();
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
#container {
 | 
			
		||||
  width: 100vw;
 | 
			
		||||
  height: 100vh;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  .comments {
 | 
			
		||||
    width: 300px !important;
 | 
			
		||||
    box-shadow: 0 4px 8px #0003;
 | 
			
		||||
  }
 | 
			
		||||
  .checkbox {
 | 
			
		||||
    width: 100vw;
 | 
			
		||||
    height: 50px;
 | 
			
		||||
    background-color: #ffffffb3;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    //left: 40%;
 | 
			
		||||
    .checkboxItem {
 | 
			
		||||
      text-align: center;
 | 
			
		||||
      line-height: 50px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,83 @@
 | 
			
		|||
body {
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
*:focus {
 | 
			
		||||
  outline: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ul {
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
li {
 | 
			
		||||
  list-style-type: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.page {
 | 
			
		||||
  width: 1024px;
 | 
			
		||||
  margin: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* input{
 | 
			
		||||
      background-color:#DA70D6 !important;
 | 
			
		||||
    } */
 | 
			
		||||
 | 
			
		||||
input:-webkit-autofill {
 | 
			
		||||
  /*自动填入文本颜色*/
 | 
			
		||||
  -webkit-text-fill-color: #ffffff !important;
 | 
			
		||||
  /*自动填入光标颜色*/
 | 
			
		||||
  caret-color: white;
 | 
			
		||||
  /*背景透明;原理:动画由透明变有背景色;参数:背景色属性、动画时间、动画速度曲线、延迟时间*/
 | 
			
		||||
  transition: background-color 0s linear 3600s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input::-webkit-input-placeholder {
 | 
			
		||||
  color: #305d9b;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input::-moz-input-placeholder {
 | 
			
		||||
  color: #305d9b;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input::-ms-input-placeholder {
 | 
			
		||||
  color: #305d9b;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input[type="button"] {
 | 
			
		||||
  border: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// body {
 | 
			
		||||
//   background: -webkit-linear-gradient(#030f2b, #152578);
 | 
			
		||||
//   background: -o-linear-gradient(#030f2b, #152578);
 | 
			
		||||
//   background: -moz-linear-gradient(#030f2b, #152578);
 | 
			
		||||
//   background: -mos-linear-gradient(#030f2b, #152578);
 | 
			
		||||
//   background: linear-gradient(#030f2b, #152578);
 | 
			
		||||
// }
 | 
			
		||||
.login {
 | 
			
		||||
  height: 100vh;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  background: url("@/assets/background-34eb3d2b.jpg") no-repeat;
 | 
			
		||||
  background-size: cover;
 | 
			
		||||
  .loginItem {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    top: 30vh;
 | 
			
		||||
    width: 50%;
 | 
			
		||||
    border: 1px solid #ccc;
 | 
			
		||||
    padding: 40px;
 | 
			
		||||
    border-radius: 10px;
 | 
			
		||||
    h1 {
 | 
			
		||||
      color: white;
 | 
			
		||||
      font-size: 40px;
 | 
			
		||||
    }
 | 
			
		||||
    h2 {
 | 
			
		||||
      margin-bottom: 20px;
 | 
			
		||||
      color: white;
 | 
			
		||||
      font-size: 20px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,117 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="login">
 | 
			
		||||
    <a-row>
 | 
			
		||||
      <a-col :span="12"></a-col>
 | 
			
		||||
      <a-col :span="12">
 | 
			
		||||
        <a-form
 | 
			
		||||
          ref="formRef"
 | 
			
		||||
          class="loginItem"
 | 
			
		||||
          :model="formState"
 | 
			
		||||
          name="login"
 | 
			
		||||
          autocomplete="off"
 | 
			
		||||
          @finish="onFinish"
 | 
			
		||||
          @finishFailed="onFinishFailed"
 | 
			
		||||
        >
 | 
			
		||||
          <h1>Hello</h1>
 | 
			
		||||
          <h2>您好,欢迎来到我的世界</h2>
 | 
			
		||||
          <a-form-item
 | 
			
		||||
            name="phoneNumber"
 | 
			
		||||
            :rules="[{ required: true, message: '请输入您的电话号码!' }]"
 | 
			
		||||
          >
 | 
			
		||||
            <a-input
 | 
			
		||||
              placeholder="请输入您的电话号码"
 | 
			
		||||
              v-model:value="formState.phoneNumber"
 | 
			
		||||
            >
 | 
			
		||||
              <template #prefix>
 | 
			
		||||
                <UserOutlined class="site-form-item-icon" />
 | 
			
		||||
              </template>
 | 
			
		||||
            </a-input>
 | 
			
		||||
          </a-form-item>
 | 
			
		||||
          <a-form-item
 | 
			
		||||
            name="password"
 | 
			
		||||
            :rules="[{ required: true, message: '请输入您的密码!' }]"
 | 
			
		||||
          >
 | 
			
		||||
            <a-input-password
 | 
			
		||||
              placeholder="请输入您的密码"
 | 
			
		||||
              v-model:value="formState.password"
 | 
			
		||||
            >
 | 
			
		||||
              <template #prefix>
 | 
			
		||||
                <LockOutlined class="site-form-item-icon" />
 | 
			
		||||
              </template>
 | 
			
		||||
            </a-input-password>
 | 
			
		||||
          </a-form-item>
 | 
			
		||||
          <a-form-item name="remember">
 | 
			
		||||
            <a-checkbox v-model:checked="formState.remember">记住我</a-checkbox>
 | 
			
		||||
          </a-form-item>
 | 
			
		||||
          <a-form-item>
 | 
			
		||||
            <a-button
 | 
			
		||||
              style="width: 100%"
 | 
			
		||||
              type="primary"
 | 
			
		||||
              :loading="loading"
 | 
			
		||||
              html-type="submit"
 | 
			
		||||
              >登录</a-button
 | 
			
		||||
            >
 | 
			
		||||
          </a-form-item>
 | 
			
		||||
        </a-form>
 | 
			
		||||
      </a-col>
 | 
			
		||||
    </a-row>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<!--  -->
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
// import api from "@/axios";
 | 
			
		||||
import { ref, computed, reactive } from "vue";
 | 
			
		||||
import aesUtil from "@/utils/aesUtil";
 | 
			
		||||
import { UserOutlined, LockOutlined } from "@ant-design/icons-vue";
 | 
			
		||||
import { useUserStore } from "@/stores/modules/userStore";
 | 
			
		||||
import { encryptStr } from "@/utils/aesUtil";
 | 
			
		||||
import { useRoute, useRouter } from "vue-router";
 | 
			
		||||
import { message } from "ant-design-vue";
 | 
			
		||||
import api from "@/axios";
 | 
			
		||||
const userStore = useUserStore();
 | 
			
		||||
const formRef = ref();
 | 
			
		||||
const loading = ref(false);
 | 
			
		||||
interface FormState {
 | 
			
		||||
  phoneNumber: string;
 | 
			
		||||
  password: string;
 | 
			
		||||
  remember: boolean;
 | 
			
		||||
}
 | 
			
		||||
const formState = reactive<FormState>({
 | 
			
		||||
  phoneNumber: "15576404472",
 | 
			
		||||
  password: "123456",
 | 
			
		||||
  remember: true,
 | 
			
		||||
});
 | 
			
		||||
// 路由对象
 | 
			
		||||
const $route = useRoute();
 | 
			
		||||
// 获取路由器
 | 
			
		||||
const $router = useRouter();
 | 
			
		||||
const onFinish = () => {
 | 
			
		||||
  userStore.tokenList('Ghdfef13156513256413565')
 | 
			
		||||
  let redirect: any = $route.query.redirect
 | 
			
		||||
  $router.push({ path: redirect || "/login" })
 | 
			
		||||
  message.success("成功登录")
 | 
			
		||||
  // loading.value = true;
 | 
			
		||||
  // const encryptedText = encryptStr(formState.password);
 | 
			
		||||
  // api
 | 
			
		||||
  //   .post("/login/management", {
 | 
			
		||||
  //     phoneNumber: formState.phoneNumber,
 | 
			
		||||
  //     password: encryptedText,
 | 
			
		||||
  //   })
 | 
			
		||||
  //   .then((res) => {
 | 
			
		||||
  //     if (res.code === 200) {
 | 
			
		||||
  //       userStore.setTokenInfo(res.data as tokenInfo);
 | 
			
		||||
  //       let redirect: any = $route.query.redirect;
 | 
			
		||||
  //       $router.push({ path: redirect || "/login" });
 | 
			
		||||
  //       message.success("成功登录");
 | 
			
		||||
  //     }
 | 
			
		||||
  //     loading.value = false;
 | 
			
		||||
  //   });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onFinishFailed = (errorInfo: any) => {
 | 
			
		||||
  console.log("Failed:", errorInfo);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
@import "./login.scss";
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
<template>
 | 
			
		||||
   <div>菜品</div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,26 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="attr">
 | 
			
		||||
    <a-card :bordered="false" style="width: 100%">
 | 
			
		||||
      <p>属性管理</p>
 | 
			
		||||
    </a-card>
 | 
			
		||||
 | 
			
		||||
    <a-card :bordered="false" class="attrItem">
 | 
			
		||||
      <p>我是表格页面</p>
 | 
			
		||||
    </a-card>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup></script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.attr {
 | 
			
		||||
  height: calc(100vh - 130px);
 | 
			
		||||
  // background: #fff;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  .attrItem {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: calc(100vh - 100px);
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    margin-top: 20px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="trademark">
 | 
			
		||||
    <a-card :bordered="false" style="width: 100%">
 | 
			
		||||
      <p>品牌管理</p>
 | 
			
		||||
    </a-card>
 | 
			
		||||
 | 
			
		||||
    <a-card :bordered="false" class="trademarkItem">
 | 
			
		||||
      <p>我是表格页面</p>
 | 
			
		||||
    </a-card>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup></script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.trademark {
 | 
			
		||||
  height: calc(100vh - 130px);
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  .trademarkItem {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: calc(100vh - 100px);
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    margin-top: 20px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div>数据大屏</div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts"></script>
 | 
			
		||||
<style lang="scss" scoped></style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div>智能分析统计</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,26 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="bgManagement">
 | 
			
		||||
    <a-card :bordered="false" style="width: 100%">
 | 
			
		||||
      <p>用户管理</p>
 | 
			
		||||
    </a-card>
 | 
			
		||||
 | 
			
		||||
    <a-card :bordered="false" class="bgManagementItem">
 | 
			
		||||
      <p>我是表格页面</p>
 | 
			
		||||
    </a-card>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup></script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.bgManagement {
 | 
			
		||||
  height: calc(100vh - 130px);
 | 
			
		||||
  // background: #fff;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  .bgManagementItem {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: calc(100vh - 100px);
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    margin-top: 20px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div>日志管理</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,26 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="permission">
 | 
			
		||||
    <a-card :bordered="false" style="width: 100%">
 | 
			
		||||
      <p>菜单管理</p>
 | 
			
		||||
    </a-card>
 | 
			
		||||
 | 
			
		||||
    <a-card :bordered="false" class="permissionItem">
 | 
			
		||||
      <p>我是表格页面</p>
 | 
			
		||||
    </a-card>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup></script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.permission {
 | 
			
		||||
  height: calc(100vh - 130px);
 | 
			
		||||
  // background: #fff;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  .permissionItem {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: calc(100vh - 100px);
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    margin-top: 20px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,26 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="role">
 | 
			
		||||
    <a-card :bordered="false" style="width: 100%">
 | 
			
		||||
      <p>角色管理</p>
 | 
			
		||||
    </a-card>
 | 
			
		||||
 | 
			
		||||
    <a-card :bordered="false" class="roleItem">
 | 
			
		||||
      <p>我是表格页面</p>
 | 
			
		||||
    </a-card>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup></script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.role {
 | 
			
		||||
  height: calc(100vh - 130px);
 | 
			
		||||
  // background: #fff;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  .roleItem {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: calc(100vh - 100px);
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    margin-top: 20px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
<script setup lang="ts">
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
<div>工作台账管理</div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,21 @@
 | 
			
		|||
/// <reference types="vite/client" />
 | 
			
		||||
interface ImportMetaEnv {
 | 
			
		||||
  //当前环境
 | 
			
		||||
  readonly VITE_APP_ENV: "development" | "production";
 | 
			
		||||
  //启动端口
 | 
			
		||||
  readonly VITE_APP_PORT: number;
 | 
			
		||||
 | 
			
		||||
  // axios
 | 
			
		||||
  readonly VITE_APP_BASE_API: string;
 | 
			
		||||
  readonly VITE_APP_PROXY_URL: string;
 | 
			
		||||
 | 
			
		||||
  // crypto-js
 | 
			
		||||
  readonly VITE_APP_CRYPTO_JS_SECRET_KEY: string;
 | 
			
		||||
  // readonly VITE_APP_CRYPTO_JS_SECRET_IV: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare module "*.vue" {
 | 
			
		||||
  import { DefineComponent } from "vue";
 | 
			
		||||
  const component: DefineComponent<{}, {}, any>;
 | 
			
		||||
  export default component;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,42 @@
 | 
			
		|||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "target": "ESNext",
 | 
			
		||||
    "useDefineForClassFields": true,
 | 
			
		||||
    "allowJs": true,
 | 
			
		||||
    "module": "ESNext",
 | 
			
		||||
    "lib": ["ESNext", "DOM", "ScriptHost"],
 | 
			
		||||
    "baseUrl": "./",
 | 
			
		||||
    "paths": {
 | 
			
		||||
      "@/*": ["src/*"]
 | 
			
		||||
    },
 | 
			
		||||
    "skipLibCheck": true,
 | 
			
		||||
    /* Bundler mode */
 | 
			
		||||
    "moduleResolution": "Node",
 | 
			
		||||
    "resolveJsonModule": true,
 | 
			
		||||
    "isolatedModules": true,
 | 
			
		||||
    "esModuleInterop": true,
 | 
			
		||||
    "jsx": "preserve",
 | 
			
		||||
    /* Linting */
 | 
			
		||||
    "strict": true,
 | 
			
		||||
    //检查未使用的局部变量
 | 
			
		||||
    //"noUnusedLocals": true,
 | 
			
		||||
    //检查switch有没有使用break
 | 
			
		||||
    "noFallthroughCasesInSwitch": true,
 | 
			
		||||
    //启用简单模式 const str:string = undefined | null;
 | 
			
		||||
    "strictNullChecks": false,
 | 
			
		||||
    //为发出的JavaScript文件创建源映射文件——用来指定编译时是否生成.map文件
 | 
			
		||||
    "sourceMap": true
 | 
			
		||||
  },
 | 
			
		||||
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
 | 
			
		||||
  "exclude": [
 | 
			
		||||
    "node_modules",
 | 
			
		||||
    "dist",
 | 
			
		||||
    "src/assets/iconfont/iconfont.js",
 | 
			
		||||
    "**/*.js"
 | 
			
		||||
  ],
 | 
			
		||||
  "references": [
 | 
			
		||||
    {
 | 
			
		||||
      "path": "./tsconfig.node.json"
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "composite": true,
 | 
			
		||||
    "module": "ESNext",
 | 
			
		||||
    "moduleResolution": "Node",
 | 
			
		||||
    "allowSyntheticDefaultImports": true
 | 
			
		||||
  },
 | 
			
		||||
  "include": ["vite.config.ts"]
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,85 @@
 | 
			
		|||
import { defineConfig, loadEnv, UserConfigExport } from "vite";
 | 
			
		||||
import vueJsx from "@vitejs/plugin-vue-jsx";
 | 
			
		||||
import vue from "@vitejs/plugin-vue";
 | 
			
		||||
import * as path from "path";
 | 
			
		||||
 | 
			
		||||
const pathSrc = path.resolve(__dirname, "src");
 | 
			
		||||
 | 
			
		||||
// https://vitejs.dev/config/
 | 
			
		||||
// loadEnv对应加装环境中的变量
 | 
			
		||||
export default ({ mode }): UserConfigExport => {
 | 
			
		||||
  // 我要加载哪一个环境的文件process.cwd()
 | 
			
		||||
  const env: Record<string, string> = loadEnv(mode, process.cwd(), "");
 | 
			
		||||
  return defineConfig({
 | 
			
		||||
    define: {
 | 
			
		||||
      __APP_ENV__: JSON.stringify(env),
 | 
			
		||||
    },
 | 
			
		||||
    base: "/",
 | 
			
		||||
    plugins: [vue(), vueJsx()],
 | 
			
		||||
    server: {
 | 
			
		||||
      host: "0.0.0.0",
 | 
			
		||||
      port: env["VITE_APP_PORT"] as unknown as number,
 | 
			
		||||
      open: false,
 | 
			
		||||
      proxy: {
 | 
			
		||||
        [env["VITE_APP_BASE_API"]]: {
 | 
			
		||||
          target: env["VITE_APP_PROXY_URL"],
 | 
			
		||||
          changeOrigin: true,
 | 
			
		||||
          secure: false,
 | 
			
		||||
          rewrite: (path) =>
 | 
			
		||||
            path.replace(RegExp(`^${env["VITE_APP_BASE_API"]}`), ""),
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    build: {
 | 
			
		||||
      outDir: "dist",
 | 
			
		||||
      target: "modules",
 | 
			
		||||
      chunkSizeWarningLimit: 1500,
 | 
			
		||||
      minify: "terser",
 | 
			
		||||
      terserOptions: {
 | 
			
		||||
        compress: {
 | 
			
		||||
          //生产环境时移除console
 | 
			
		||||
          drop_console: env["VITE_DROP_CONSOLE"] as unknown as boolean,
 | 
			
		||||
          drop_debugger: env["VITE_DROP_CONSOLE"] as unknown as boolean,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      rollupOptions: {
 | 
			
		||||
        output: {
 | 
			
		||||
          manualChunks(id) {
 | 
			
		||||
            if (id.includes("node_modules")) {
 | 
			
		||||
              return id
 | 
			
		||||
                .toString()
 | 
			
		||||
                .split("node_modules/")[1]
 | 
			
		||||
                .split("/")[0]
 | 
			
		||||
                .toString();
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
          chunkFileNames(chunkInfo) {
 | 
			
		||||
            const facadeModuleId = chunkInfo.facadeModuleId
 | 
			
		||||
              ? chunkInfo.facadeModuleId.split("/")
 | 
			
		||||
              : [];
 | 
			
		||||
            const fileName =
 | 
			
		||||
              facadeModuleId[facadeModuleId.length - 2] || "[name]";
 | 
			
		||||
            return `js/${fileName}/[name].[hash].js`;
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    resolve: {
 | 
			
		||||
      alias: {
 | 
			
		||||
        "@": pathSrc,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    css: {
 | 
			
		||||
      preprocessorOptions: {
 | 
			
		||||
        scss: {
 | 
			
		||||
          charset: false,
 | 
			
		||||
          // 全局css变量
 | 
			
		||||
          additionalData: `
 | 
			
		||||
                    @import "@/assets/scss/variable.scss";
 | 
			
		||||
                    @import "@/assets/scss/mixin.scss";
 | 
			
		||||
                    `,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
		Loading…
	
		Reference in New Issue