第一次
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