新架构

This commit is contained in:
TimSpan 2025-04-28 14:13:49 +08:00
parent 89c09e5a6b
commit 6f737791bd
183 changed files with 10662 additions and 7556 deletions

View File

@ -1,12 +1,14 @@
VITE_APP_NAME=保安管理
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
# VITE_APP_PROXY_URL=121321
#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
#jsencrypt 跟后端公钥保持一致
VITE_APP_JS_ENCRYPT_PUBLIC_KEY=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCo58dwFmtTkiA4bj/FiBqlYsiKOugT/WCQRYFPY49A7y6VEswzMihgxSz0B/UDdekM9PDsKy06Gjy8BpWu+ikR1ms+/PbMJOmw5jGUeiswf8uqJDDquHG5oJJk0o7J8/1JvzmpbN/Ctjcm2yUscTfG2WvvY0ViwnptYU7+ZkRcuQIDAQAB
# minio
VITE_APP_MINIO_URL=http://172.10.10.238:9000

View File

@ -1,12 +1,12 @@
VITE_APP_ENV=production
VITE_APP_PORT=9528
VITE_DROP_CONSOLE=true
VITE_APP_NAME=保安管理
VITE_APP_ENV=development
VITE_APP_PORT=9527
VITE_DROP_CONSOLE=false
# axios
VITE_APP_BASE_API=/api
VITE_APP_PROXY_URL=https://localhost:8083
VITE_APP_PROXY_URL='http://172.10.10.93:1233'
VITE_APP_JS_ENCRYPT_PUBLIC_KEY=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCo58dwFmtTkiA4bj/FiBqlYsiKOugT/WCQRYFPY49A7y6VEswzMihgxSz0B/UDdekM9PDsKy06Gjy8BpWu+ikR1ms+/PbMJOmw5jGUeiswf8uqJDDquHG5oJJk0o7J8/1JvzmpbN/Ctjcm2yUscTfG2WvvY0ViwnptYU7+ZkRcuQIDAQAB
#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
# minio
VITE_APP_MINIO_URL=http://172.10.10.238:9000

17
.gitignore vendored
View File

@ -8,17 +8,32 @@ pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
analyze.html
auto-imports.d.ts
components.d.ts
yarn.lock
package-lock.json
*.zip
.env.locality
analyzer.html

9
.prettierrc Normal file
View File

@ -0,0 +1,9 @@
{
"vueIndentScriptAndStyle": true,
"htmlWhitespaceSensitivity": "ignore",
"printWidth": 80,
"singleAttributePerLine": true
}

View File

@ -1,18 +1,33 @@
# Vue 3 + TypeScript + Vite
# management
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.
This template should help get you started developing with Vue 3 in Vite.
## 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).
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Type Support For `.vue` Imports in TS
## 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.
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 [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) 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:
## Customize configuration
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.
See [Vite Configuration Reference](https://vite.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Type-Check, Compile and Minify for Production
```sh
npm run build
```

View File

@ -1,13 +1,16 @@
<!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 lang="">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<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>

5430
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,40 +1,47 @@
{
"name": "web",
"private": true,
"name": "management",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite --mode development",
"pro": "vite --mode production",
"build": "vite build --mode production",
"preview": "vite preview"
"local": "vite --mode locality",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build"
},
"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",
"@vueuse/core": "^13.1.0",
"animate.css": "^4.1.1",
"axios": "^1.8.1",
"dayjs": "^1.11.13",
"event-source-polyfill": "^1.0.31",
"js-base64": "^3.7.7",
"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"
"pinia": "^3.0.1",
"pinia-plugin-persistedstate": "^4.2.0",
"sass": "^1.85.1",
"terser": "^5.39.0",
"unplugin-auto-import": "^19.1.1",
"unplugin-vue-components": "^28.4.1",
"vue": "^3.5.13",
"vue-router": "^4.5.0",
"naive-ui": "^2.41.0"
},
"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"
"@tsconfig/node22": "^22.0.0",
"@types/event-source-polyfill": "^1.0.5",
"@types/node": "^22.13.4",
"@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^4.1.1",
"@vue/tsconfig": "^0.7.0",
"npm-run-all2": "^7.0.2",
"rollup-plugin-visualizer": "^5.14.0",
"typescript": "~5.7.3",
"vite": "^6.1.0",
"vite-plugin-vue-devtools": "^7.7.2",
"vue-tsc": "^2.2.2"
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,13 +1,25 @@
<template>
<!-- <a-config-provider> -->
<router-view></router-view>
<!-- </a-config-provider> -->
<n-config-provider
:theme="settingsStore.theme"
:locale="zhCN"
:dateLocale="dateZhCN"
>
<n-loading-bar-provider>
<n-notification-provider>
<n-modal-provider>
<n-message-provider>
<n-dialog-provider>
<router-view></router-view>
</n-dialog-provider>
</n-message-provider>
</n-modal-provider>
</n-notification-provider>
</n-loading-bar-provider>
</n-config-provider>
</template>
<script setup lang="ts"></script>
<style lang="scss">
body {
font-family: AliBaBaPuHuTi, serif;
}
</style>
<script setup lang="ts">
import { useSettingsStore } from "@/stores/settings";
const settingsStore = useSettingsStore();
import { dateZhCN, zhCN } from "naive-ui";
</script>

View File

@ -1,4 +0,0 @@
@font-face {
font-family: AliBaBaPuHuTi;
src: url('../font/AlibabaPuHuiTi-2-65-Medium.ttf');
}

View File

@ -1,53 +0,0 @@
/* 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;
}

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
src/assets/images/fb.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

1
src/assets/logo.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

After

Width:  |  Height:  |  Size: 276 B

View File

@ -1,19 +1,51 @@
/* flex */
.flx-center {
.flex-center {
display: flex;
align-items: center;
justify-content: center;
}
.flx-justify-between {
.flex-end {
display: flex;
align-items: center;
justify-content: flex-end;
}
.flex-justify-between {
display: flex;
align-items: center;
justify-content: space-between;
}
.flx-align-center {
.flex-align-center {
display: flex;
align-items: center;
}
.flex-column-center {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.w-f {
width: 100%;
}
.h-f {
height: 100%;
}
.f-r {
float: right;
}
.f-l {
float: left;
}
/* clearfix */
.clearfix::after {
display: block;
@ -49,11 +81,13 @@
.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;
@ -64,34 +98,13 @@
.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} {
@ -429,3 +442,46 @@
padding-top: 25px;
padding-bottom: 25px;
}
/*高德地图去水印*/
.amap-logo {
display: none !important;
visibility: hidden !important;
}
.amap-copyright {
display: none !important;
visibility: hidden !important;
}
/*高德地图去水印 结束*/
/*浏览器滚动条样式*/
::-webkit-scrollbar {
width: 4px;
height: 8px;
background-color: #fff;
}
::-webkit-scrollbar-thumb {
display: block;
min-height: 12px;
min-width: 8px;
border-radius: 6px;
background-color: rgb(217, 217, 217);
}
::-webkit-scrollbar-thumb:hover {
display: block;
min-height: 12px;
min-width: 8px;
border-radius: 6px;
background-color: rgb(159, 159, 159);
}
/*浏览器滚动条样式 结束*/
/* 鼠标悬浮手指 */
.pointer {
cursor: pointer;
}

View File

@ -1,4 +0,0 @@
@font-face {
font-family: AliBaBaPuHuTi;
src: url('../font/AlibabaPuHuiTi-2-65-Medium.ttf');
}

View File

@ -1,4 +0,0 @@
@mixin wh($w, $h) {
width: $w;
height: $h;
}

View File

@ -1,53 +0,0 @@
/* 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;
}

View File

@ -1,2 +0,0 @@
/* global css variable */
$primary-color: var(--el-color-primary);

View File

@ -1,2 +0,0 @@
/* 存放css变量 */
$primary-color: red;

View File

@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 496 B

View File

@ -1,22 +1,18 @@
import axios, {
AxiosError,
AxiosInstance,
AxiosRequestConfig,
AxiosResponse,
InternalAxiosRequestConfig,
type AxiosInstance,
type AxiosRequestConfig,
type AxiosResponse,
} 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;
}
import router from "@/router";
import Loading from "@/shared/classes/Loading";
import { message } from "@/utils";
import { CLIENT_TYPE, LOGIN_ROUTER } from "@/config/constant";
import { useUserStore } from "@/stores/user";
const axiosConfig: AxiosRequestConfig = {
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: import.meta.env.VITE_APP_ENV === "production" ? 10000 : 60000,
timeout:
import.meta.env.VITE_APP_ENV === "production" ? 10 * 1000 : 60 * 1000,
timeoutErrorMessage: "请求超时......",
};
@ -27,30 +23,49 @@ class RequestHttp {
this.service = axios.create(config);
//1.添加请求拦截
this.service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
//默认带上用户token
config.headers.set("token", useUserStore().userInfo?.tokenValue);
(config) => {
const userStore = useUserStore();
//请求必须带上当前客户端类型
config.headers.set("Client-Type", CLIENT_TYPE);
config.headers.set(
userStore.tokenInfo?.tokenName,
userStore.tokenInfo?.tokenValue
);
if (config.loading) {
Loading.show(config.loadingMessage);
}
//todo 默认带上用户token
return config;
},
(error: AxiosError): Promise<string> => {
message.error(error.message).then((r) => {});
async (error: AxiosError): Promise<string> => {
Loading.close();
message.error(error.message);
return Promise.reject(error);
}
);
//2.添加响应拦截
this.service.interceptors.response.use(
(response: AxiosResponse): Promise<any> => {
async (response: AxiosResponse): Promise<any> => {
Loading.close();
const jsonResult: JsonResult<unknown> = response.data;
if (jsonResult.code !== 200) {
//todo 一些特定的错误需要重新登录 这里暂时没处理
message.error(jsonResult.message).then((r) => {});
if (jsonResult && jsonResult.code !== 200) {
//todo 一些特定的错误需要重新登录
if ([-1].includes(jsonResult.code)) {
//清除登录信息
useUserStore().resetUserInfo();
//跳转登录页
await router.push({
path: LOGIN_ROUTER.path,
});
}
message.error(jsonResult.message);
return Promise.reject(jsonResult);
}
return Promise.resolve(jsonResult);
},
(error: AxiosError): Promise<string> => {
message.error(error.message).then((r) => {});
async (error: AxiosError): Promise<string> => {
Loading.close();
message.error(error.message);
return Promise.reject(error);
}
);

View File

@ -0,0 +1,20 @@
<template>
<n-button
@click="toggle"
quaternary
>
<Icon
:name="
isFullscreen
? 'MaterialSymbolsFullscreenExit'
: 'MaterialSymbolsFullscreen'
"
/>
</n-button>
</template>
<script setup lang="ts">
import { Icon } from "@/components/icon";
import { useFullscreen } from "@vueuse/core";
const { isFullscreen, toggle } = useFullscreen();
</script>

View File

@ -1,42 +0,0 @@
<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>

View File

@ -0,0 +1,16 @@
import { Base64 } from "js-base64";
export const onlinePreview = (url: string) =>
"http://172.10.10.238:8012/onlinePreview?url=" +
encodeURIComponent(Base64.encode(url));
import api from "@/axios";
interface OBJ {
objectUrl: string;
preSignedUrl: string;
}
// export const getPreSignedUrl = async (fileName: string) => {
// const resp = await api.get<OBJ>("/file/getPreSignedUrl", {
// fileName,
// });
// return resp.data as OBJ;
// };

View File

@ -0,0 +1,96 @@
<template>
<n-upload
:custom-request="handleCustomRequest"
:max="1"
:show-file-list="true"
accept="*"
>
<div style="display: flex">
<n-button dashed>
<Icon name="LineMdUpload" />
<span>选择文件上传</span>
</n-button>
<n-button
style="margin-left: 10px"
@click.stop="handlePreview"
:disabled="!previewURL"
>
<span>预览文件</span>
</n-button>
</div>
</n-upload>
</template>
<script setup lang="tsx">
import axios from "@/axios";
import { message } from "@/utils/index";
import { Icon } from "@/components/icon/index";
import { getPreSignedUrl, onlinePreview } from "./index";
import { type UploadCustomRequestOptions, useModal } from "naive-ui";
const previewURL = defineModel<string>("value", { required: true });
const modal = useModal();
const fileName = ref("");
const show = ref(true);
const modalRef = ref();
const handleCustomRequest = async (options: UploadCustomRequestOptions) => {
const { file, onFinish, onError } = options;
try {
fileName.value = file.name;
const { preSignedUrl, objectUrl } = await getPreSignedUrl(fileName.value);
const finalURL = __APP_ENV.VITE_APP_MINIO_URL + objectUrl;
console.log(
"_________________________ ~ handleCustomRequest ~ finalURL:",
finalURL
);
await axios.put(preSignedUrl, file.file as File);
onFinish();
previewURL.value = finalURL;
message.success("上传成功!");
} catch (err) {
console.error(err);
onError?.();
message.error("上传失败!");
}
};
const handlePreview = () => {
if (!previewURL.value) {
message.warning("没有可预览的文件");
return;
}
modalRef.value = modal.create({
title: `${fileName.value} 预览`,
preset: "card",
style: {
width: "800px",
},
content: () => (
<n-spin show={show.value}>
<iframe
src={onlinePreview(previewURL.value)}
style={{ width: "100%", height: "50vh" }}
onLoad={() => (show.value = false)}
/>
</n-spin>
),
footer: () => (
<n-space class="flex-end">
<n-button
type="info"
onClick={() =>
window.open(onlinePreview(previewURL.value), "_blank")
}
>
新窗口预览
</n-button>
<n-button onClick={() => modalRef.value?.destroy()}>关闭</n-button>
</n-space>
),
onAfterLeave: () => {
modalRef.value?.destroy();
},
});
};
</script>

View File

@ -0,0 +1,65 @@
<template>
<div style="display: flex">
<n-image
v-if="!!previewURL"
height="50"
:src="previewURL"
/>
<n-upload
@remove="onRemove"
:custom-request="handleCustomRequest"
:max="1"
:show-file-list="true"
accept="*"
>
<n-button
dashed
style="margin-right: 5px;"
>
<Icon name="LineMdUpload" />
<span>选择照片上传</span>
</n-button>
</n-upload>
</div>
</template>
<script setup lang="tsx">
import axios from "@/axios";
import { message } from "@/utils/index";
import { getPreSignedUrl } from "../customFileUpload/index";
import {
type UploadCustomRequestOptions,
type UploadFileInfo,
} from "naive-ui";
const fileName = ref("");
const previewURL = defineModel<string>("value", { required: true });
const handleCustomRequest = async (options: UploadCustomRequestOptions) => {
const { file, onFinish, onError } = options;
try {
fileName.value = file.name;
const { preSignedUrl, objectUrl } = await getPreSignedUrl(fileName.value);
const finalURL = __APP_ENV.VITE_APP_MINIO_URL + objectUrl;
console.log(
"_________________________ ~ handleCustomRequest ~ finalURL:",
finalURL
);
await axios.put(preSignedUrl, file.file as File);
onFinish();
previewURL.value = finalURL;
message.success("上传成功!");
} catch (err) {
console.error(err);
onError?.();
message.error("上传失败!");
}
};
const onRemove = (options: {
file: UploadFileInfo;
fileList: Array<UploadFileInfo>;
index: number;
}): Promise<boolean> | boolean | any => {
console.log("onRemove");
previewURL.value = "";
};
</script>

View File

View File

@ -0,0 +1,157 @@
<template>
<n-form
ref="formRef" :model="formData" :inline="true" label-placement="left" class="dynamic-form">
<div v-for="(item, index) in formData.items" :key="index">
<div style="display: flex">
<n-form-item
:label="`${userName} (${index + 1})`"
>
<n-input v-model:value="item.name" clearable />
</n-form-item>
<n-form-item
:label="`${userPhone} (${index + 1})`"
>
<n-input v-model:value="item.phone" clearable />
</n-form-item>
<n-form-item
:label="`${userIdCard} (${index + 1})`"
>
<n-input v-model:value="item.idCard" clearable />
</n-form-item>
<n-form-item>
<n-button type="error" secondary @click="removeItem(index)" v-if="index !== 0" :disabled="formData.items.length === 1">
{{ removeButtonText }}
</n-button>
</n-form-item>
</div>
</div>
<n-button type="primary" @click="addItem" class="add-btn">
{{ addButtonText }}
</n-button>
</n-form>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from "vue";
import type { FormInst, FormRules } from "naive-ui";
interface FormItem {
name: string;
phone: string;
idCard: string;
}
const props = withDefaults(defineProps<{
modelValue: FormItem[]
addButtonText?: string
removeButtonText?: string
userName:string,
userPhone:'',
userIdCard:''
}>(), {
modelValue: () => [{
name: "",
phone: "",
idCard: ""
}],
addButtonText: '添加',
removeButtonText: '删除',
userName:'',
userPhone:'',
userIdCard:''
});
const emit = defineEmits<{
'update:modelValue': [value: FormItem[]];
'add': [index: number];
'remove': [index: number];
}>();
//
const rules: FormRules = {
name: {
required: true,
message: `${props.userName}不能为空`,
trigger: [ 'input']
},
phone: {
required: true,
validator: (_, value: string) => {
if (!/^1[3-9]\d{9}$/.test(value)) {
return new Error("请输入有效的手机号码");
}
return true;
},
trigger: [ 'input']
},
idCard: {
required: true,
validator: (_, value: string) => {
if (!/(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(value)) {
return new Error("请输入有效的身份证号码");
}
return true;
},
trigger: [ 'input']
}
};
//
const formRef = ref<FormInst | null>(null);
const formData = reactive<{ items: FormItem[] }>({
items: [...props.modelValue]
});
//
watch(() => props.modelValue, (newVal) => {
if (JSON.stringify(newVal) !== JSON.stringify(formData.items)) {
formData.items = [...newVal];
}
});
//
const addItem = () => {
formData.items.push({
name: "",
phone: "",
idCard: ""
});
emit('update:modelValue', formData.items);
emit('add', formData.items.length - 1);
};
//
const removeItem = (index: number) => {
if (formData.items.length > 1) {
formData.items.splice(index, 1);
emit('update:modelValue', formData.items);
emit('remove', index);
}
};
//
const validate = () => {
return new Promise((resolve, reject) => {
formRef.value?.validate((errors) => {
if (!errors) {
resolve(formData.items);
} else {
reject(errors);
}
});
});
};
//
defineExpose({ validate });
</script>
<style lang="scss" scoped>
.dynamic-form {
display: flex;
flex-wrap: wrap;
width: 100%;
.add-btn{
margin-left: 10px;
}
}
</style>

View File

@ -1,54 +1,30 @@
<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
style="
height: 100vh;
width: 100vw;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
"
>
<n-result
status="404"
title="404 资源不存在"
description="生活总归带点荒谬"
>
<template #footer>
<n-button @click.prevent="toHome">返回主页</n-button>
</template>
</n-result>
</div>
</template>
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
import { useRouter } from "vue-router";
const router = useRouter();
function toHome() {
router.push("/");
}
</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>

View File

@ -1,7 +0,0 @@
<script setup lang="ts"></script>
<template>
<div>500</div>
</template>
<style scoped></style>

View File

@ -0,0 +1,335 @@
// import {
// type Component,
// type PropType,
// type VNode,
// computed,
// defineComponent,
// h,
// ref,
// } from "vue";
// import { formProps } from "naive-ui/es/form/src/Form";
// import type { FormInst, LabelPlacement } from "naive-ui/es/form/src/interface";
// import {
// NCascader,
// NCheckbox,
// NCheckboxGroup,
// NDatePicker,
// NForm,
// NFormItemGi,
// NGrid,
// NInput,
// NInputNumber,
// NRadio,
// NRadioGroup,
// NSelect,
// NSpace,
// NTimePicker,
// NTreeSelect,
// type GridProps,
// } from "naive-ui";
// import type {
// Fm,
// FormItemOptions,
// FormModelValue,
// FormProInst,
// FormProItemProps,
// } from "../src/indexface";
// export const formProProps = {
// ...formProps,
// labelWidth: {
// type: [Number, String] as PropType<number | string>,
// },
// labelPlacement: {
// type: String as PropType<LabelPlacement>,
// default: "left",
// },
// value: {
// type: Object as PropType<any>,
// },
// gridProps: {
// type: Object as PropType<GridProps>,
// default: {
// cols: 1,
// xGap: 8,
// itemResponsive: true,
// responsive: "self",
// },
// },
// formItemOptions: {
// type: Object as PropType<FormItemOptions<any>>,
// },
// };
// export default defineComponent({
// name: "FormPro",
// props: formProProps,
// emits: ["update:value"],
// setup(props, { emit, expose }) {
// //表单实例
// const formRef = ref<FormInst>();
// //表单值
// const formModelValue = ref<FormModelValue>(props.value || {});
// //初始化一些配置属性
// const formOptions = computed<any>(() => {
// if (!props.formItemOptions) {
// return {};
// }
// return Object.fromEntries(
// Object.entries(props.formItemOptions).map(([key, value]) => {
// const item = { ...value };
// item.path = key;
// //设置默认校验规则
// item.required &&
// (item.rule = { required: true, message: `${item.label}不能为空` });
// //初始化组件配置
// !item.componentsProps && (item.componentsProps = {});
// //设置提示词
// !item.componentsProps.placeholder &&
// (item.componentsProps.placeholder = item.type.includes("input")
// ? `请输入${item.label}`
// : `请选择${item.label}`);
// //默认可清除
// item.componentsProps?.clearable ??
// (item.componentsProps.clearable = true);
// return [key, item];
// })
// );
// });
// expose<FormProInst<any>>({
// validate: () => formRef.value!.validate(),
// restoreValidation: () => formRef.value!.restoreValidation(),
// });
// return () => (
// <>
// <NForm ref={formRef} {...props} model={formModelValue.value}>
// <NGrid {...props.gridProps}>
// {Object.entries(formOptions.value).map(([field, item]) => {
// return (
// <NFormItemGi {...item} key={field}>
// {comps[item.type](formModelValue.value, field, item)}
// </NFormItemGi>
// );
// })}
// </NGrid>
// </NForm>
// </>
// );
// },
// });
// const comps: Record<
// keyof Fm,
// (
// modelValue: { [k in string]: any },
// field: string,
// itemProps: FormProItemProps<any, any>
// ) => VNode
// > = {
// custom: (modelValue, _, itemProps: FormProItemProps<any, "custom">) => {
// if (itemProps.customRender) {
// return itemProps.customRender(modelValue);
// } else {
// return h(itemProps.defineComponent as Component, {
// modelValue,
// });
// }
// },
// input: (modelValue, field, itemProps: FormProItemProps<any, "input">) => {
// return h(
// NInput,
// {
// style: {
// width: "100%",
// },
// ...itemProps.componentsProps,
// value: modelValue[field],
// "onUpdate:value": (val) => (modelValue[field] = val),
// },
// {
// ...itemProps.componentsSlots,
// }
// );
// },
// inputNumber: (
// modelValue,
// field,
// itemProps: FormProItemProps<any, "inputNumber">
// ) => {
// return h(
// NInputNumber,
// {
// style: {
// width: "100%",
// },
// ...itemProps.componentsProps,
// value: modelValue[field],
// "onUpdate:value": (val) => (modelValue[field] = val),
// },
// {
// ...itemProps.componentsSlots,
// }
// );
// },
// radioGroup: (
// modelValue,
// field,
// itemProps: FormProItemProps<any, "radioGroup">
// ) => {
// return h(
// NRadioGroup,
// {
// style: {
// width: "100%",
// },
// ...itemProps.componentsProps,
// value: modelValue[field],
// "onUpdate:value": (val) => (modelValue[field] = val),
// },
// {
// default: () => (
// <NSpace>
// {itemProps.options?.map((o) => {
// return (
// //@ts-ignore 这里类型检查会有问题 其实没有问题
// <NRadio key={o.value} value={o.value}>
// {o.label}
// </NRadio>
// );
// })}
// </NSpace>
// ),
// }
// );
// },
// checkboxGroup: (
// modelValue,
// field,
// itemProps: FormProItemProps<any, "checkboxGroup">
// ) => {
// return h(
// NCheckboxGroup,
// {
// style: {
// width: "100%",
// },
// ...itemProps.componentsProps,
// value: modelValue[field],
// "onUpdate:value": (val) => (modelValue[field] = val),
// },
// {
// default: () => {
// return (
// <NSpace>
// {itemProps.options?.map((o) => {
// return (
// //@ts-ignore 这里类型检查会有问题 其实没有问题
// <NCheckbox key={o.value} value={o.value} label={o.label} />
// );
// })}
// </NSpace>
// );
// },
// ...itemProps.componentsSlots,
// }
// );
// },
// select: (modelValue, field, itemProps: FormProItemProps<any, "select">) => {
// return h(
// NSelect,
// {
// style: {
// width: "100%",
// },
// ...itemProps.componentsProps,
// value: modelValue[field],
// "onUpdate:value": (val) => (modelValue[field] = val),
// },
// {
// ...itemProps.componentsSlots,
// }
// );
// },
// treeSelect: (
// modelValue,
// field,
// itemProps: FormProItemProps<any, "treeSelect">
// ) => {
// return h(
// NTreeSelect,
// {
// style: {
// width: "100%",
// },
// ...itemProps.componentsProps,
// value: modelValue[field],
// "onUpdate:value": (val) => (modelValue[field] = val),
// },
// {
// ...itemProps.componentsSlots,
// }
// );
// },
// cascader: (
// modelValue,
// field,
// itemProps: FormProItemProps<any, "cascader">
// ) => {
// return h(
// NCascader,
// {
// style: {
// width: "100%",
// },
// ...itemProps.componentsProps,
// value: modelValue[field],
// "onUpdate:value": (val) => (modelValue[field] = val),
// },
// {
// ...itemProps.componentsSlots,
// }
// );
// },
// datePicker: (
// modelValue,
// field,
// itemProps: FormProItemProps<any, "datePicker">
// ) => {
// return h(
// NDatePicker,
// {
// style: {
// width: "100%",
// },
// ...itemProps.componentsProps,
// value: modelValue[field],
// "onUpdate:value": (val) => (modelValue[field] = val),
// },
// {
// ...itemProps.componentsSlots,
// }
// );
// },
// timePicker: (
// modelValue,
// field,
// itemProps: FormProItemProps<any, "timePicker">
// ) => {
// return h(
// NTimePicker,
// {
// style: {
// width: "100%",
// },
// ...itemProps.componentsProps,
// value: modelValue[field],
// "onUpdate:value": (val) => (modelValue[field] = val),
// },
// {
// ...itemProps.componentsSlots,
// }
// );
// },
// };

View File

@ -0,0 +1,111 @@
<template>
<FormPro ref="form" v-model:value="modelValue" :form-item-options="formOptions" />
{{ modelValue }}
</template>
<script setup lang="tsx">
import { defineComponent, markRaw, reactive, useTemplateRef } from 'vue'
import { FormPro, type FormItemOptions, type FormProInst } from '@/components'
const formRef = useTemplateRef<FormProInst>('form')
interface A {
name: string
age: number
info: string
text: string
text2: string
like: string
aaa: string[]
}
const modelValue = reactive<A>({
name: 'zs',
age: 12,
info: 'asdsadas',
text: '222',
text2: '',
like: '',
aaa: [],
})
const formOptions = reactive<FormItemOptions<A>>({
name: {
type: 'input',
label: '名字',
componentsProps: {
disabled: false,
},
},
like: {
type: 'radioGroup',
label: '爱好',
options: [
{
value: 0,
label: '唱',
},
{
value: 1,
label: '跳',
},
{
value: 2,
label: 'rap',
},
],
},
aaa: {
type: 'checkboxGroup',
label: 'aaa',
options: [
{
value: 0,
label: '唱',
},
{
value: 1,
label: '跳',
},
{
value: 2,
label: 'rap',
},
],
},
age: {
type: 'inputNumber',
label: '年龄',
},
info: {
type: 'input',
label: '介绍',
componentsProps: {},
required: true,
},
text: {
type: 'custom',
label: '文件',
customRender: (modelValue) => {
return <n-input v-model:value={modelValue.text}></n-input>
},
},
text2: {
type: 'custom',
label: '文件',
defineComponent: markRaw(
defineComponent(
({ modelValue }) => {
return () => <n-input v-model:value={modelValue.text2}></n-input>
},
{ props: ['modelValue'] }
)
),
},
})
setTimeout(() => {
formOptions.name.componentsProps.disabled = true
}, 3000)
</script>
<style scoped></style>

View File

@ -0,0 +1,3 @@
export { formProProps } from './src/FormPro'
export type { FormModelValue, FormProProps, FormItemOptions, FormProItemProps, FormProInst } from './src/indexface'
export { default as FormPro } from './src/FormPro.vue'

View File

@ -0,0 +1,51 @@
import { type PropType } from "vue";
import { formProps } from "naive-ui/es/form/src/Form";
import type {
FormItemRuleValidator,
LabelAlign,
LabelPlacement,
} from "naive-ui/es/form/src/interface";
import type { FormItemOptions } from "./indexface";
import { type FormItemProps, type GridProps } from "naive-ui";
import { isEmpty } from "lodash-es";
export const formProProps = {
...formProps,
labelWidth: {
type: [Number, String] as PropType<number | string>,
},
labelPlacement: {
type: String as PropType<LabelPlacement>,
default: "left",
},
labelAlign: {
type: String as PropType<LabelAlign>,
default: "left",
},
value: {
type: Object as PropType<any>,
},
gridProps: {
type: Object as PropType<GridProps>,
default: {
cols: 1,
xGap: 8,
itemResponsive: true,
responsive: "self",
},
},
formItemOptions: {
type: Object as PropType<FormItemOptions<any>>,
},
};
// 定义通用校验规则
export const universalValidator: FormItemRuleValidator = (_, value) => {
if (value === null || value === undefined) {
return false;
} else if (typeof value === "string" && value.trim() === "") {
return false;
} else if (Array.isArray(value) && value.length === 0) {
return false;
}
};

View File

@ -0,0 +1,97 @@
<template>
<n-form
ref="formInst"
v-bind="props"
:model="formModelValue"
>
<n-grid v-bind="props.gridProps">
<n-form-item-gi
v-for="(item, field) in formOptions"
:key="field"
v-bind="item"
>
<template #label>
<label style="display: inline-flex; align-items: center">
<span>
{{ item.label }}
</span>
<template v-if="item.remarkRender">
<n-popover trigger="hover">
<template #trigger>
<Icon
name="LucideCircleHelp"
color="#ff4a36"
:size="18"
/>
</template>
<template #default>
<component :is="item.remarkRender" />
</template>
</n-popover>
</template>
</label>
</template>
<component
:is="FormSupportComp[item.type](formModelValue, field, item)"
/>
</n-form-item-gi>
</n-grid>
</n-form>
</template>
<script setup lang="ts" generic="T extends FormModelValue">
import { computed, useTemplateRef } from "vue";
import { formProProps, universalValidator } from "./FormPro";
import type { FormModelValue, FormProInst } from "./indexface";
import type { FormInst } from "naive-ui";
import FormSupportComp from "./FormSupportComp";
import { cloneDeep } from "lodash-es";
import { Icon } from "@/components/icon";
//
const formRef = useTemplateRef<FormInst>("formInst");
const props = defineProps(formProProps);
const formModelValue = defineModel<T>("value", { default: {} });
//
const formOptions = computed(() => {
if (!props.formItemOptions) {
return {};
}
return Object.fromEntries(
Object.entries(props.formItemOptions).map(([key, value]) => {
const item = cloneDeep(value);
item.path = key;
// rule
item.required &&
!item.rule &&
(item.rule = {
required: true,
message: `${item.label}不能为空`,
validator: universalValidator,
trigger: "blur",
});
//
!item.componentsProps && (item.componentsProps = {});
//
!item.componentsProps.placeholder &&
(item.componentsProps.placeholder = item.type.includes("input")
? `请输入${item.label}`
: `请选择${item.label}`);
//
item.componentsProps?.clearable ??
(item.componentsProps.clearable = true);
return [key, item];
})
);
});
defineExpose<FormProInst>({
validate: () => formRef.value!.validate(),
restoreValidation: () => formRef.value!.restoreValidation(),
});
</script>
<style scoped></style>

View File

@ -0,0 +1,194 @@
import { type DefineComponent, type VNode } from "vue";
import type { Fm, FormProItemProps } from "./indexface";
import {
NCascader,
NCheckbox,
NCheckboxGroup,
NDatePicker,
NInput,
NInputNumber,
NRadio,
NRadioGroup,
NSelect,
NSpace,
NTimePicker,
NTreeSelect,
} from "naive-ui";
export default {
custom: (modelValue, _, itemProps: FormProItemProps<any, "custom">) => {
if (itemProps.customRender) {
return itemProps.customRender(modelValue);
} else {
const Df = itemProps.defineComponent as DefineComponent<{
modelValue: typeof modelValue;
}>;
return <Df modelValue={modelValue} />;
}
},
input: (modelValue, field, itemProps: FormProItemProps<any, "input">) => {
return (
<NInput
style={"width:100%"}
v-model:value={modelValue[field]}
{...itemProps.componentsProps}
v-slots={{ ...itemProps.componentsSlots }}
/>
);
},
inputNumber: (
modelValue,
field,
itemProps: FormProItemProps<any, "inputNumber">
) => {
return (
<NInputNumber
style={"width:100%"}
v-model:value={modelValue[field]}
{...itemProps.componentsProps}
v-slots={{ ...itemProps.componentsSlots }}
/>
);
},
radioGroup: (
modelValue,
field,
itemProps: FormProItemProps<any, "radioGroup">
) => {
return (
<NRadioGroup
style={"width:100%"}
v-model:value={modelValue[field]}
{...itemProps.componentsProps}
v-slots={{
default: () => (
<NSpace>
{(itemProps.options as SelectNodeVo<any>[])?.map((o) => {
return (
<NRadio
key={o.value}
value={o.value}
>
{o.label}
</NRadio>
);
})}
</NSpace>
),
...itemProps.componentsSlots,
}}
/>
);
},
checkboxGroup: (
modelValue,
field,
itemProps: FormProItemProps<any, "checkboxGroup">
) => {
return (
<NCheckboxGroup
style={"width:100%"}
v-model:value={modelValue[field]}
{...itemProps.componentsProps}
v-slots={{
default: () => (
<NSpace>
{(itemProps.options as SelectNodeVo<any>[])?.map((o) => {
return (
<NCheckbox
key={o.value}
value={o.value}
label={o.label}
/>
);
})}
</NSpace>
),
...itemProps.componentsSlots,
}}
/>
);
},
select: (modelValue, field, itemProps: FormProItemProps<any, "select">) => {
return (
<NSelect
style={"width:100%"}
v-model:value={modelValue[field]}
options={itemProps.options as any[]}
labelField="label"
valueField="value"
childrenField="children"
{...itemProps.componentsProps}
v-slots={{ ...itemProps.componentsSlots }}
/>
);
},
treeSelect: (
modelValue,
field,
itemProps: FormProItemProps<any, "treeSelect">
) => {
return (
<NTreeSelect
style={"width:100%"}
v-model:value={modelValue[field]}
options={itemProps.options as any[]}
labelField="label"
keyField="value"
childrenField="children"
{...itemProps.componentsProps}
v-slots={{ ...itemProps.componentsSlots }}
/>
);
},
cascader: (
modelValue,
field,
itemProps: FormProItemProps<any, "cascader">
) => {
return (
<NCascader
style={"width:100%"}
v-model:value={modelValue[field]}
options={itemProps.options as any[]}
{...itemProps.componentsProps}
v-slots={{ ...itemProps.componentsSlots }}
/>
);
},
datePicker: (
modelValue,
field,
itemProps: FormProItemProps<any, "datePicker">
) => {
return (
<NDatePicker
style={"width:100%"}
v-model:value={modelValue[field]}
{...itemProps.componentsProps}
v-slots={{ ...itemProps.componentsSlots }}
/>
);
},
timePicker: (
modelValue,
field,
itemProps: FormProItemProps<any, "timePicker">
) => {
return (
<NTimePicker
style={"width:100%"}
v-model:value={modelValue[field]}
{...itemProps.componentsProps}
v-slots={{ ...itemProps.componentsSlots }}
/>
);
},
} as Record<
keyof Fm,
(
modelValue: { [k in string]: any },
field: any,
itemProps: FormProItemProps<any, any>
) => VNode
>;

View File

@ -0,0 +1,118 @@
import type {
CascaderProps,
CascaderSlots,
CheckboxGroupProps,
DatePickerProps,
DatePickerSlots,
FormInst,
FormItemGiProps,
FormProps,
GridProps,
InputNumberProps,
InputNumberSlots,
InputProps,
InputSlots,
RadioGroupProps,
SelectProps,
SelectSlots,
TimePickerProps,
TimePickerSlots,
TreeSelectProps,
TreeSelectSlots,
} from "naive-ui";
import type { Component, Ref, VNode } from "vue";
import type { JSX } from "vue/jsx-runtime";
/**
*
*/
export type FormModelValue = { [k in string]: any };
/**
*
*/
type DefaultSlots = {
default: () => any;
};
type CompPropsAndSlots<P, S> = {
props: P;
slots: S;
};
/**
*
*/
export type Fm = {
custom: CompPropsAndSlots<any, any>;
input: CompPropsAndSlots<InputProps, InputSlots>;
inputNumber: CompPropsAndSlots<InputNumberProps, InputNumberSlots>;
radioGroup: CompPropsAndSlots<RadioGroupProps, DefaultSlots>;
checkboxGroup: CompPropsAndSlots<CheckboxGroupProps, DefaultSlots>;
select: CompPropsAndSlots<SelectProps, SelectSlots>;
treeSelect: CompPropsAndSlots<TreeSelectProps, TreeSelectSlots>;
cascader: CompPropsAndSlots<CascaderProps, CascaderSlots>;
datePicker: CompPropsAndSlots<DatePickerProps, DatePickerSlots>;
timePicker: CompPropsAndSlots<TimePickerProps, TimePickerSlots>;
};
/**
*
*/
type OptionType =
| (SelectNodeVo<any> | TreeNodeVo<any>)[]
| Ref<SelectNodeVo<any>[] | TreeNodeVo<any>[]>;
type BaseFormProItemProps<ModelValue> = FormItemGiProps & {
/**
*
*/
remarkRender?: JSX.Element;
/**
*
*/
options?: OptionType;
/**
* type为custom时 render函数跟defineComponent render函数无状态 defineComponent有状态
* @param modelValue
*/
customRender?: (modelValue: ModelValue) => VNode;
defineComponent?: Component<{ modelValue: ModelValue }>;
};
/**
*
*/
export type FormProItemProps<
ModelValue,
TK extends keyof Fm
> = BaseFormProItemProps<ModelValue> & {
type: TK;
componentsProps?: Fm[TK]["props"];
componentsSlots?: Fm[TK]["slots"];
};
/**
*
*/
export type FormItemOptions<ModelValue> = {
[key in keyof ModelValue]: {
[T in keyof Fm]: FormProItemProps<ModelValue, T>;
}[keyof Fm];
};
/**
*
*/
export type FormProProps<ModalValue = {}> = FormProps & {
value: FormModelValue;
"onUpdate:value": Function;
onUpdateValue: Function;
gridProps?: GridProps;
formItemOptions?: FormItemOptions<ModalValue>;
};
/**
*
*/
export type FormProInst<ModalValue extends FormModelValue = {}> = {} & FormInst;

View File

@ -0,0 +1,3 @@
export type { IconProps } from "./src/interface";
export { default as Icon } from "./src/Icon.vue";
export { default as SelectIcon } from "./src/SelectIcon.vue";

View File

@ -0,0 +1,56 @@
<template>
<div class="icon-container">
<component
:is="iconComponent?.component"
:style="styles"
:class="props.class"
/>
<span>
<slot></slot>
</span>
</div>
</template>
<script setup lang="ts">
import { computed, type CSSProperties } from "vue";
import type { IconProps } from "./interface";
import { iconMap } from "./icon";
const props = withDefaults(defineProps<IconProps>(), {
size: 20,
color: "currentColor",
});
const iconComponent = computed(() => iconMap.get(props.name));
const styles = computed(() => {
const style: CSSProperties = {};
if (typeof props.size === "number") {
style.width = `${props.size}px`;
style.height = `${props.size}px`;
} else {
style.width = props.size;
style.height = props.size;
}
if (props.color) {
style.color = props.color;
}
if (typeof props.style === "string") {
return props.style;
}
return {
...style,
...props.style,
};
});
</script>
<style lang="scss" scoped>
.icon-container {
display: inline-flex;
align-items: center;
justify-content: center;
}
</style>

View File

@ -0,0 +1,7 @@
<template><n-select v-bind="props">
</n-select></template>
<script setup lang="ts">
import { selectIconProps } from './selectIcon';
const props = defineProps(selectIconProps)
</script>

View File

@ -0,0 +1,6 @@
<template>
<Icon name="LineMdMenuFoldRight" />
<Icon name="LineMdMenuFoldRight" />
</template>
<script setup lang="ts">
</script>

View File

@ -0,0 +1,23 @@
import { type Component } from "vue";
import { markRaw } from "vue";
export const modules = import.meta.glob("./icons/*.vue", { eager: true });
// 创建图标映射
export const iconMap = new Map<
string,
{
title: string;
component: Component;
}
>();
Object.values(modules).forEach((module: any) => {
const component = module.default;
if (component?.name) {
iconMap.set(component.name, {
title: component.title,
component: markRaw(component),
});
}
});

View File

@ -0,0 +1,24 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 32 32"
>
<!-- Icon from Carbon by IBM - undefined -->
<path
fill="currentColor"
d="M16 4c6.6 0 12 5.4 12 12s-5.4 12-12 12S4 22.6 4 16S9.4 4 16 4m0-2C8.3 2 2 8.3 2 16s6.3 14 14 14s14-6.3 14-14S23.7 2 16 2"
></path>
<path
fill="currentColor"
d="M8 15h16v2H8z"
></path>
</svg>
</template>
<script lang="ts">
export default {
name: "CarbonSubtractAlt",
};
</script>

View File

@ -0,0 +1,28 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 16 16"
>
<!-- Icon from Codicons by Microsoft Corporation - https://github.com/microsoft/vscode-codicons/blob/main/LICENSE -->
<g
fill="currentColor"
fillRule="evenodd"
clipRule="evenodd"
>
<path
d="m8.621 8.086l-.707-.707L6.5 8.793L5.086 7.379l-.707.707L5.793 9.5l-1.414 1.414l.707.707L6.5 10.207l1.414 1.414l.707-.707L7.207 9.5z"
></path>
<path
d="m5 3l1-1h7l1 1v7l-1 1h-2v2l-1 1H3l-1-1V6l1-1h2zm1 2h4l1 1v4h2V3H6zm4 1H3v7h7z"
></path>
</g>
</svg>
</template>
<script lang="ts">
export default {
name: "CodiconCloseAll",
};
</script>

View File

@ -0,0 +1,23 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 16 16"
>
<!-- Icon from Codicons by Microsoft Corporation - https://github.com/microsoft/vscode-codicons/blob/main/LICENSE -->
<path
fill="currentColor"
fillRule="evenodd"
d="M9.111 4.663A2 2 0 1 1 6.89 1.337a2 2 0 0 1 2.222 3.326zm-.555-2.494A1 1 0 1 0 7.444 3.83a1 1 0 0 0 1.112-1.66zm2.61.03a1.494 1.494 0 0 1 1.895.188a1.513 1.513 0 0 1-.487 2.46a1.49 1.49 0 0 1-1.635-.326a1.512 1.512 0 0 1 .228-2.321zm.48 1.61a.499.499 0 1 0 .705-.708a.5.5 0 0 0-.351-.15a.5.5 0 0 0-.5.503a.5.5 0 0 0 .146.356zM3.19 12.487H5v1.005H3.19a1.2 1.2 0 0 1-.842-.357a1.2 1.2 0 0 1-.348-.85v-1.81a1 1 0 0 1-.71-.332A1 1 0 0 1 1 9.408V7.226c.003-.472.19-.923.52-1.258c.329-.331.774-.52 1.24-.523H4.6a2.9 2.9 0 0 0-.55 1.006H2.76a.8.8 0 0 0-.54.232a.78.78 0 0 0-.22.543v2.232h1v2.826a.2.2 0 0 0 .05.151a.24.24 0 0 0 .14.05zm7.3-6.518a1.77 1.77 0 0 0-1.25-.523H6.76a1.77 1.77 0 0 0-1.24.523c-.33.335-.517.786-.52 1.258v3.178a1.06 1.06 0 0 0 .29.734a1 1 0 0 0 .71.332v2.323a1.2 1.2 0 0 0 .35.855c.18.168.407.277.65.312h2a1.15 1.15 0 0 0 1-1.167V11.47a1 1 0 0 0 .71-.332a1 1 0 0 0 .29-.734V7.226a1.8 1.8 0 0 0-.51-1.258zM10 10.454H9v3.34a.2.2 0 0 1-.06.14a.17.17 0 0 1-.14.06H7.19a.21.21 0 0 1-.2-.2v-3.34H6V7.226c0-.203.079-.398.22-.543a.8.8 0 0 1 .54-.232h2.48a.78.78 0 0 1 .705.48a.8.8 0 0 1 .055.295zm2.81 3.037H11v-1.005h1.8a.24.24 0 0 0 .14-.05a.2.2 0 0 0 .06-.152V9.458h1V7.226a.78.78 0 0 0-.22-.543a.8.8 0 0 0-.54-.232h-1.29a2.9 2.9 0 0 0-.55-1.006h1.84a1.77 1.77 0 0 1 1.24.523c.33.335.517.786.52 1.258v2.182c0 .273-.103.535-.289.733c-.186.199-.44.318-.711.333v1.81c0 .319-.125.624-.348.85a1.2 1.2 0 0 1-.842.357M4 1.945a1.49 1.49 0 0 0-1.386.932A1.52 1.52 0 0 0 2.94 4.52A1.497 1.497 0 0 0 5.5 3.454c0-.4-.158-.784-.44-1.067A1.5 1.5 0 0 0 4 1.945m0 2.012a.5.5 0 0 1-.5-.503a.504.504 0 0 1 .5-.503a.51.51 0 0 1 .5.503a.504.504 0 0 1-.5.503"
clipRule="evenodd"
></path>
</svg>
</template>
<script lang="ts">
export default {
name: "CodiconOrganization",
title: "部门管理",
};
</script>

View File

@ -0,0 +1,25 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from EOS Icons by SUSE UX/UI team - https://gitlab.com/SUSE-UIUX/eos-icons/-/blob/master/LICENSE -->
<path
fill="currentColor"
d="M15 21h-2a2 2 0 0 1 0-4h2v-2h-2a4 4 0 0 0 0 8h2Zm8-2a4 4 0 0 1-4 4h-2v-2h2a2 2 0 0 0 0-4h-2v-2h2a4 4 0 0 1 4 4"
></path>
<path
fill="currentColor"
d="M14 18h4v2h-4zm-7 1a6 6 0 0 1 .09-1H3v-1.4c0-2 4-3.1 6-3.1a8.6 8.6 0 0 1 1.35.125A5.95 5.95 0 0 1 13 13h5V4a2.006 2.006 0 0 0-2-2h-4.18a2.988 2.988 0 0 0-5.64 0H2a2.006 2.006 0 0 0-2 2v14a2.006 2.006 0 0 0 2 2h5.09A6 6 0 0 1 7 19M9 2a1 1 0 1 1-1 1a1.003 1.003 0 0 1 1-1m0 4a3 3 0 1 1-3 3a2.996 2.996 0 0 1 3-3"
></path>
</svg>
</template>
<script lang="ts">
export default {
name: "EosIconsRoleBinding",
title: "角色管理",
};
</script>

View File

@ -0,0 +1,29 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from EOS Icons by SUSE UX/UI team - https://gitlab.com/SUSE-UIUX/eos-icons/-/blob/master/LICENSE -->
<path
fill="currentColor"
d="M23 19a4 4 0 0 1-4 4h-2v-2h2a2 2 0 0 0 0-4h-2v-2h2a4 4 0 0 1 4 4M9 19a4 4 0 0 1 4-4h2v2h-2a2 2 0 0 0 0 4h2v2h-2a4 4 0 0 1-4-4"
></path>
<path
fill="currentColor"
d="M14 18h4v2h-4zM9 5a3 3 0 1 0 3 3a3.01 3.01 0 0 0-3-3m0 4a1 1 0 1 1 1-1a1.003 1.003 0 0 1-1 1m-3.69 6A7 7 0 0 1 9 13.88a6 6 0 0 1 .778.064A5.97 5.97 0 0 1 13 13h.254A9.4 9.4 0 0 0 9 11.89c-2.03 0-6 1.07-6 3.58V17h4.349a6 6 0 0 1 1.188-2Z"
></path>
<path
fill="currentColor"
d="M16 2h-4.18a2.988 2.988 0 0 0-5.64 0H2a2.006 2.006 0 0 0-2 2v14a2.006 2.006 0 0 0 2 2h5.141a3.6 3.6 0 0 1 0-2H2V4h14v9h2V4a2.006 2.006 0 0 0-2-2M9 3.25a.756.756 0 0 1-.75-.75a.75.75 0 0 1 1.5 0a.756.756 0 0 1-.75.75"
></path>
</svg>
</template>
<script lang="ts">
export default {
name: "EosIconsRoleBindingOutlined",
title: "角色管理",
};
</script>

View File

@ -0,0 +1,20 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from Google Material Icons by Material Design Authors - https://github.com/material-icons/material-icons/blob/master/LICENSE -->
<path
fill="currentColor"
d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6l-6-6z"
></path>
</svg>
</template>
<script lang="ts">
export default {
name: "IcBaselineKeyboardArrowDown",
};
</script>

View File

@ -0,0 +1,27 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from IconaMoon by Dariush Habibpour - https://creativecommons.org/licenses/by/4.0/ -->
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
>
<path d="M3 12a9 9 0 0 1 16-5.658"></path>
<path d="M19.5 3v4h-4m5.5 5a9 9 0 0 1-16 5.657"></path>
<path d="M4.5 21v-4h4"></path>
</g>
</svg>
</template>
<script lang="ts">
export default {
name: "IconamoonSynchronize",
};
</script>

View File

@ -0,0 +1,46 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from Material Line Icons by Vjacheslav Trushkin - https://github.com/cyberalien/line-md/blob/master/license.txt -->
<g
fill="none"
stroke="currentColor"
stroke-dasharray="28"
stroke-dashoffset="28"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
>
<path d="M4 21v-1c0 -3.31 2.69 -6 6 -6h4c3.31 0 6 2.69 6 6v1">
<animate
fill="freeze"
attributeName="stroke-dashoffset"
dur="0.4s"
values="28;0"
></animate>
</path>
<path
d="M12 11c-2.21 0 -4 -1.79 -4 -4c0 -2.21 1.79 -4 4 -4c2.21 0 4 1.79 4 4c0 2.21 -1.79 4 -4 4Z"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.4s"
dur="0.4s"
values="28;0"
></animate>
</path>
</g>
</svg>
</template>
<script lang="ts">
export default {
name: "LineMdAccount",
title: "用户",
};
</script>

View File

@ -0,0 +1,110 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from Material Line Icons by Vjacheslav Trushkin - https://github.com/cyberalien/line-md/blob/master/license.txt -->
<mask id="lineMdCogFilledLoop0">
<defs>
<symbol id="lineMdCogFilledLoop1">
<path
d="M11 13L15.74 5.5C16.03 5.67 16.31 5.85 16.57 6.05C16.57 6.05 16.57 6.05 16.57 6.05C16.64 6.1 16.71 6.16 16.77 6.22C18.14 7.34 19.09 8.94 19.4 10.75C19.41 10.84 19.42 10.92 19.43 11C19.43 11 19.43 11 19.43 11C19.48 11.33 19.5 11.66 19.5 12z"
>
<animate
fill="freeze"
attributeName="d"
begin="0.5s"
dur="0.2s"
values="M11 13L15.74 5.5C16.03 5.67 16.31 5.85 16.57 6.05C16.57 6.05 16.57 6.05 16.57 6.05C16.64 6.1 16.71 6.16 16.77 6.22C18.14 7.34 19.09 8.94 19.4 10.75C19.41 10.84 19.42 10.92 19.43 11C19.43 11 19.43 11 19.43 11C19.48 11.33 19.5 11.66 19.5 12z;M11 13L15.74 5.5C16.03 5.67 16.31 5.85 16.57 6.05C16.57 6.05 19.09 5.04 19.09 5.04C19.25 4.98 19.52 5.01 19.6 5.17C19.6 5.17 21.67 8.75 21.67 8.75C21.77 8.92 21.73 9.2 21.6 9.32C21.6 9.32 19.43 11 19.43 11C19.48 11.33 19.5 11.66 19.5 12z"
></animate>
</path>
</symbol>
</defs>
<g
fill="none"
stroke="#fff"
stroke-width="2"
>
<path
stroke-dasharray="36"
stroke-dashoffset="36"
stroke-width="5"
d="M12 7c2.76 0 5 2.24 5 5c0 2.76 -2.24 5 -5 5c-2.76 0 -5 -2.24 -5 -5c0 -2.76 2.24 -5 5 -5Z"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
dur="0.5s"
values="36;0"
></animate>
<set
fill="freeze"
attributeName="opacity"
begin="0.5s"
to="0"
></set>
</path>
<g
fill="#fff"
stroke="none"
opacity="0"
>
<use href="#lineMdCogFilledLoop1"></use>
<use
href="#lineMdCogFilledLoop1"
transform="rotate(60 12 12)"
></use>
<use
href="#lineMdCogFilledLoop1"
transform="rotate(120 12 12)"
></use>
<use
href="#lineMdCogFilledLoop1"
transform="rotate(180 12 12)"
></use>
<use
href="#lineMdCogFilledLoop1"
transform="rotate(240 12 12)"
></use>
<use
href="#lineMdCogFilledLoop1"
transform="rotate(300 12 12)"
></use>
<set
fill="freeze"
attributeName="opacity"
begin="0.5s"
to="1"
></set>
<animateTransform
fill="freeze"
attributeName="transform"
dur="30s"
type="rotate"
values="0 12 12;360 12 12"
></animateTransform>
</g>
</g>
<circle
cx="12"
cy="12"
r="3.5"
></circle>
</mask>
<rect
width="24"
height="24"
fill="currentColor"
mask="url(#lineMdCogFilledLoop0)"
></rect>
</svg>
</template>
<script lang="ts">
export default {
name: "LineMdCogFilledLoop",
title: "设置",
};
</script>

View File

@ -0,0 +1,77 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from Material Line Icons by Vjacheslav Trushkin - https://github.com/cyberalien/line-md/blob/master/license.txt -->
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
>
<path
stroke-dasharray="64"
stroke-dashoffset="64"
d="M13 3l6 6v12h-14v-18h8"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
dur="0.6s"
values="64;0"
></animate>
</path>
<path
stroke-dasharray="14"
stroke-dashoffset="14"
stroke-width="1"
d="M12.5 3v5.5h6.5"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.7s"
dur="0.2s"
values="14;0"
></animate>
</path>
<path
stroke-dasharray="8"
stroke-dashoffset="8"
d="M10 13l-2 2l2 2"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.9s"
dur="0.2s"
values="8;0"
></animate>
</path>
<path
stroke-dasharray="8"
stroke-dashoffset="8"
d="M14 13l2 2l-2 2"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="1.1s"
dur="0.2s"
values="8;0"
></animate>
</path>
</g>
</svg>
</template>
<script lang="ts">
export default {
name: "LineMdDocumentCode",
title: "开发设置",
};
</script>

View File

@ -0,0 +1,91 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from Material Line Icons by Vjacheslav Trushkin - https://github.com/cyberalien/line-md/blob/master/license.txt -->
<mask id="lineMdFileUploadFilled0">
<g
fill="none"
stroke="#fff"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
>
<path
fill="#fff"
fillOpacity="0"
stroke-dasharray="64"
stroke-dashoffset="64"
d="M13.5 3l5.5 5.5v11.5c0 0.55 -0.45 1 -1 1h-12c-0.55 0 -1 -0.45 -1 -1v-16c0 -0.55 0.45 -1 1 -1Z"
>
<animate
fill="freeze"
attributeName="fill-opacity"
begin="0.6s"
dur="0.5s"
values="0;1"
></animate>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
dur="0.6s"
values="64;0"
></animate>
</path>
<path
fill="#000"
stroke="#000"
d="M14.5 3.5l0 4.5l4.5 0z"
opacity="0"
>
<set
fill="freeze"
attributeName="opacity"
begin="0.6s"
to="1"
></set>
</path>
<path
d="M13.5 3l5.5 5.5"
opacity="0"
>
<set
fill="freeze"
attributeName="opacity"
begin="0.6s"
to="1"
></set>
</path>
<path
fill="#000"
stroke="none"
d="M12 18l4 0h-2.5v0h-3v0h-2.5z"
>
<animate
fill="freeze"
attributeName="d"
begin="1.1s"
dur="0.2s"
values="M12 18l4 0h-2.5v0h-3v0h-2.5z;M12 11l4 4h-2.5v3h-3v-3h-2.5Z"
></animate>
</path>
</g>
</mask>
<rect
width="24"
height="24"
fill="currentColor"
mask="url(#lineMdFileUploadFilled0)"
></rect>
</svg>
</template>
<script lang="ts">
export default {
name: "LineMdFileUploadFilled",
title: "文件上传",
};
</script>

View File

@ -0,0 +1,24 @@
<template><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em"
viewBox="0 0 24 24"><!-- Icon from Material Line Icons by Vjacheslav Trushkin - https://github.com/cyberalien/line-md/blob/master/license.txt -->
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path stroke-dasharray="16" stroke-dashoffset="16" d="M5 21h14">
<animate fill="freeze" attributeName="stroke-dashoffset" dur="0.2s" values="16;0"></animate>
</path>
<path stroke-dasharray="14" stroke-dashoffset="14" d="M5 21v-13M19 21v-13">
<animate fill="freeze" attributeName="stroke-dashoffset" begin="0.2s" dur="0.2s" values="14;0"></animate>
</path>
<path stroke-dasharray="24" stroke-dashoffset="24" d="M9 21v-8h6v8">
<animate fill="freeze" attributeName="stroke-dashoffset" begin="0.4s" dur="0.4s" values="24;0"></animate>
</path>
<path stroke-dasharray="28" stroke-dashoffset="28" d="M2 10l10 -8l10 8">
<animate fill="freeze" attributeName="stroke-dashoffset" begin="0.5s" dur="0.6s" values="28;0"></animate>
</path>
</g>
</svg></template>
<script lang="ts">
export default {
name: 'LineMdHomeMd',
title:'首页'
}
</script>

View File

@ -0,0 +1,63 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from Material Line Icons by Vjacheslav Trushkin - https://github.com/cyberalien/line-md/blob/master/license.txt -->
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
>
<path
stroke-dasharray="16"
stroke-dashoffset="16"
d="M5 21h14"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
dur="0.2s"
values="16;0"
></animate>
</path>
<path
stroke-dasharray="14"
stroke-dashoffset="14"
d="M5 21v-13M19 21v-13"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.2s"
dur="0.2s"
values="14;0"
></animate>
</path>
<path
stroke-dasharray="28"
stroke-dashoffset="28"
d="M2 10l10 -8l10 8"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.4s"
dur="0.6s"
values="28;0"
></animate>
</path>
</g>
</svg>
</template>
<script lang="ts">
export default {
name: "LineMdHomeSimple",
title: "主页",
};
</script>

View File

@ -0,0 +1,128 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from Material Line Icons by Vjacheslav Trushkin - https://github.com/cyberalien/line-md/blob/master/license.txt -->
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
>
<path
stroke-dasharray="2"
stroke-dashoffset="2"
d="M4 5h0.01"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
dur="0.1s"
values="2;0"
></animate>
</path>
<path
stroke-dasharray="14"
stroke-dashoffset="14"
d="M8 5h12"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.1s"
dur="0.2s"
values="14;0"
></animate>
</path>
<path
stroke-dasharray="2"
stroke-dashoffset="2"
d="M4 10h0.01"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.3s"
dur="0.1s"
values="2;0"
></animate>
</path>
<path
stroke-dasharray="14"
stroke-dashoffset="14"
d="M8 10h12"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.4s"
dur="0.2s"
values="14;0"
></animate>
</path>
<path
stroke-dasharray="2"
stroke-dashoffset="2"
d="M4 15h0.01"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.6s"
dur="0.1s"
values="2;0"
></animate>
</path>
<path
stroke-dasharray="14"
stroke-dashoffset="14"
d="M8 15h12"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.7s"
dur="0.2s"
values="14;0"
></animate>
</path>
<path
stroke-dasharray="2"
stroke-dashoffset="2"
d="M4 20h0.01"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.9s"
dur="0.1s"
values="2;0"
></animate>
</path>
<path
stroke-dasharray="14"
stroke-dashoffset="14"
d="M8 20h12"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="1s"
dur="0.2s"
values="14;0"
></animate>
</path>
</g>
</svg>
</template>
<script lang="ts">
export default {
name: "LineMdList",
title: "扁平化菜单",
};
</script>

View File

@ -0,0 +1,128 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from Material Line Icons by Vjacheslav Trushkin - https://github.com/cyberalien/line-md/blob/master/license.txt -->
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
>
<path
stroke-dasharray="2"
stroke-dashoffset="2"
d="M4 5h0.01"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
dur="0.1s"
values="2;0"
></animate>
</path>
<path
stroke-dasharray="14"
stroke-dashoffset="14"
d="M8 5h12"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.1s"
dur="0.2s"
values="14;0"
></animate>
</path>
<path
stroke-dasharray="2"
stroke-dashoffset="2"
d="M6 10h0.01"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.3s"
dur="0.1s"
values="2;0"
></animate>
</path>
<path
stroke-dasharray="14"
stroke-dashoffset="14"
d="M10 10h10"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.4s"
dur="0.2s"
values="14;0"
></animate>
</path>
<path
stroke-dasharray="2"
stroke-dashoffset="2"
d="M8 15h0.01"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.6s"
dur="0.1s"
values="2;0"
></animate>
</path>
<path
stroke-dasharray="14"
stroke-dashoffset="14"
d="M12 15h8"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.7s"
dur="0.2s"
values="14;0"
></animate>
</path>
<path
stroke-dasharray="2"
stroke-dashoffset="2"
d="M10 20h0.01"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.9s"
dur="0.1s"
values="2;0"
></animate>
</path>
<path
stroke-dasharray="14"
stroke-dashoffset="14"
d="M14 20h6"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="1s"
dur="0.2s"
values="14;0"
></animate>
</path>
</g>
</svg>
</template>
<script lang="ts">
export default {
name: "LineMdListIndented",
title: "菜单管理",
};
</script>

View File

@ -0,0 +1,62 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from Material Line Icons by Vjacheslav Trushkin - https://github.com/cyberalien/line-md/blob/master/license.txt -->
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
>
<path
stroke-dasharray="48"
stroke-dashoffset="48"
d="M16 5v-1c0 -0.55 -0.45 -1 -1 -1h-9c-0.55 0 -1 0.45 -1 1v16c0 0.55 0.45 1 1 1h9c0.55 0 1 -0.45 1 -1v-1"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
dur="0.6s"
values="48;0"
></animate>
</path>
<path
stroke-dasharray="12"
stroke-dashoffset="12"
d="M10 12h11"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.7s"
dur="0.2s"
values="12;0"
></animate>
</path>
<path
stroke-dasharray="6"
stroke-dashoffset="6"
d="M21 12l-3.5 -3.5M21 12l-3.5 3.5"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.9s"
dur="0.2s"
values="6;0"
></animate>
</path>
</g>
</svg>
</template>
<script lang="ts">
export default {
name: "LineMdLogOut",
};
</script>

View File

@ -0,0 +1,24 @@
<template><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em"
viewBox="0 0 24 24"><!-- Icon from Material Line Icons by Vjacheslav Trushkin - https://github.com/cyberalien/line-md/blob/master/license.txt -->
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path stroke-dasharray="16" stroke-dashoffset="16" d="M19 5h-14">
<animate fill="freeze" attributeName="stroke-dashoffset" dur="0.2s" values="16;0"></animate>
</path>
<path stroke-dasharray="10" stroke-dashoffset="10" d="M19 12h-9">
<animate fill="freeze" attributeName="stroke-dashoffset" begin="0.2s" dur="0.2s" values="10;0"></animate>
</path>
<path stroke-dasharray="16" stroke-dashoffset="16" d="M19 19h-14">
<animate fill="freeze" attributeName="stroke-dashoffset" begin="0.4s" dur="0.2s" values="16;0"></animate>
</path>
<path stroke-dasharray="10" stroke-dashoffset="10" d="M7 9l-3 3l3 3">
<animate fill="freeze" attributeName="stroke-dashoffset" begin="0.6s" dur="0.2s" values="10;0"></animate>
</path>
</g>
</svg></template>
<script lang="ts">
export default {
name: 'LineMdMenuFoldLeft',
title:"菜单向左折叠"
}
</script>

View File

@ -0,0 +1,24 @@
<template><svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em"
viewBox="0 0 24 24"><!-- Icon from Material Line Icons by Vjacheslav Trushkin - https://github.com/cyberalien/line-md/blob/master/license.txt -->
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path stroke-dasharray="16" stroke-dashoffset="16" d="M5 5h14">
<animate fill="freeze" attributeName="stroke-dashoffset" dur="0.2s" values="16;0"></animate>
</path>
<path stroke-dasharray="10" stroke-dashoffset="10" d="M5 12h9">
<animate fill="freeze" attributeName="stroke-dashoffset" begin="0.2s" dur="0.2s" values="10;0"></animate>
</path>
<path stroke-dasharray="16" stroke-dashoffset="16" d="M5 19h14">
<animate fill="freeze" attributeName="stroke-dashoffset" begin="0.4s" dur="0.2s" values="16;0"></animate>
</path>
<path stroke-dasharray="10" stroke-dashoffset="10" d="M17 9l3 3l-3 3">
<animate fill="freeze" attributeName="stroke-dashoffset" begin="0.6s" dur="0.2s" values="10;0"></animate>
</path>
</g>
</svg></template>
<script lang="ts">
export default {
name: 'LineMdMenuFoldRight',
title:"菜单向右折叠"
}
</script>

View File

@ -0,0 +1,163 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from Material Line Icons by Vjacheslav Trushkin - https://github.com/cyberalien/line-md/blob/master/license.txt -->
<g
fill="none"
stroke="currentColor"
stroke-dasharray="4"
stroke-dashoffset="4"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M13 4h1.5M13 4h-1.5M13 4v1.5M13 4v-1.5">
<animate
id="lineMdMoonAltLoop0"
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.7s;lineMdMoonAltLoop0.begin+6s"
dur="0.4s"
values="4;0"
></animate>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="lineMdMoonAltLoop0.begin+2s;lineMdMoonAltLoop0.begin+4s"
dur="0.4s"
values="4;0"
></animate>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="lineMdMoonAltLoop0.begin+1.2s;lineMdMoonAltLoop0.begin+3.2s;lineMdMoonAltLoop0.begin+5.2s"
dur="0.4s"
values="0;4"
></animate>
<set
fill="freeze"
attributeName="d"
begin="lineMdMoonAltLoop0.begin+1.8s"
to="M12 5h1.5M12 5h-1.5M12 5v1.5M12 5v-1.5"
></set>
<set
fill="freeze"
attributeName="d"
begin="lineMdMoonAltLoop0.begin+3.8s"
to="M12 4h1.5M12 4h-1.5M12 4v1.5M12 4v-1.5"
></set>
<set
fill="freeze"
attributeName="d"
begin="lineMdMoonAltLoop0.begin+5.8s"
to="M13 4h1.5M13 4h-1.5M13 4v1.5M13 4v-1.5"
></set>
</path>
<path d="M19 11h1.5M19 11h-1.5M19 11v1.5M19 11v-1.5">
<animate
id="lineMdMoonAltLoop1"
fill="freeze"
attributeName="stroke-dashoffset"
begin="1.1s;lineMdMoonAltLoop1.begin+6s"
dur="0.4s"
values="4;0"
></animate>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="lineMdMoonAltLoop1.begin+2s;lineMdMoonAltLoop1.begin+4s"
dur="0.4s"
values="4;0"
></animate>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="lineMdMoonAltLoop1.begin+1.2s;lineMdMoonAltLoop1.begin+3.2s;lineMdMoonAltLoop1.begin+5.2s"
dur="0.4s"
values="0;4"
></animate>
<set
fill="freeze"
attributeName="d"
begin="lineMdMoonAltLoop1.begin+1.8s"
to="M17 11h1.5M17 11h-1.5M17 11v1.5M17 11v-1.5"
></set>
<set
fill="freeze"
attributeName="d"
begin="lineMdMoonAltLoop1.begin+3.8s"
to="M18 12h1.5M18 12h-1.5M18 12v1.5M18 12v-1.5"
></set>
<set
fill="freeze"
attributeName="d"
begin="lineMdMoonAltLoop1.begin+5.8s"
to="M19 11h1.5M19 11h-1.5M19 11v1.5M19 11v-1.5"
></set>
</path>
<path d="M19 4h1.5M19 4h-1.5M19 4v1.5M19 4v-1.5">
<animate
id="lineMdMoonAltLoop2"
fill="freeze"
attributeName="stroke-dashoffset"
begin="2s;lineMdMoonAltLoop2.begin+6s"
dur="0.4s"
values="4;0"
></animate>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="lineMdMoonAltLoop2.begin+2s"
dur="0.4s"
values="4;0"
></animate>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="lineMdMoonAltLoop2.begin+1.2s;lineMdMoonAltLoop2.begin+3.2s"
dur="0.4s"
values="0;4"
></animate>
<set
fill="freeze"
attributeName="d"
begin="lineMdMoonAltLoop2.begin+1.8s"
to="M20 5h1.5M20 5h-1.5M20 5v1.5M20 5v-1.5"
></set>
<set
fill="freeze"
attributeName="d"
begin="lineMdMoonAltLoop2.begin+5.8s"
to="M19 4h1.5M19 4h-1.5M19 4v1.5M19 4v-1.5"
></set>
</path>
</g>
<path
fill="none"
stroke="currentColor"
stroke-dasharray="56"
stroke-dashoffset="56"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M7 6 C7 12.08 11.92 17 18 17 C18.53 17 19.05 16.96 19.56 16.89 C17.95 19.36 15.17 21 12 21 C7.03 21 3 16.97 3 12 C3 8.83 4.64 6.05 7.11 4.44 C7.04 4.95 7 5.47 7 6 Z"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
dur="0.6s"
values="56;0"
></animate>
</path>
</svg>
</template>
<script lang="ts">
export default {
name: "LineMdMoonAltLoop",
title: "夜间主题",
};
</script>

View File

@ -0,0 +1,155 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from Material Line Icons by Vjacheslav Trushkin - https://github.com/cyberalien/line-md/blob/master/license.txt -->
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
>
<path
stroke-dasharray="2"
stroke-dashoffset="2"
d="M12 19v1M19 12h1M12 5v-1M5 12h-1"
>
<animate
fill="freeze"
attributeName="d"
begin="0.6s"
dur="0.2s"
values="M12 19v1M19 12h1M12 5v-1M5 12h-1;M12 21v1M21 12h1M12 3v-1M3 12h-1"
></animate>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.6s"
dur="0.2s"
values="2;0"
></animate>
</path>
<path
stroke-dasharray="2"
stroke-dashoffset="2"
d="M17 17l0.5 0.5M17 7l0.5 -0.5M7 7l-0.5 -0.5M7 17l-0.5 0.5"
>
<animate
fill="freeze"
attributeName="d"
begin="0.8s"
dur="0.2s"
values="M17 17l0.5 0.5M17 7l0.5 -0.5M7 7l-0.5 -0.5M7 17l-0.5 0.5;M18.5 18.5l0.5 0.5M18.5 5.5l0.5 -0.5M5.5 5.5l-0.5 -0.5M5.5 18.5l-0.5 0.5"
></animate>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.8s"
dur="0.2s"
values="2;0"
></animate>
</path>
<animateTransform
attributeName="transform"
dur="30s"
repeatCount="indefinite"
type="rotate"
values="0 12 12;360 12 12"
></animateTransform>
</g>
<mask id="lineMdMoonAltToSunnyOutlineLoopTransition0">
<circle
cx="12"
cy="12"
r="12"
fill="#fff"
></circle>
<circle
cx="12"
cy="12"
r="8"
>
<animate
fill="freeze"
attributeName="r"
dur="0.4s"
values="8;4"
></animate>
</circle>
<circle
cx="18"
cy="6"
r="12"
fill="#fff"
>
<animate
fill="freeze"
attributeName="cx"
dur="0.4s"
values="18;22"
></animate>
<animate
fill="freeze"
attributeName="cy"
dur="0.4s"
values="6;2"
></animate>
<animate
fill="freeze"
attributeName="r"
dur="0.4s"
values="12;3"
></animate>
</circle>
<circle
cx="18"
cy="6"
r="10"
>
<animate
fill="freeze"
attributeName="cx"
dur="0.4s"
values="18;22"
></animate>
<animate
fill="freeze"
attributeName="cy"
dur="0.4s"
values="6;2"
></animate>
<animate
fill="freeze"
attributeName="r"
dur="0.4s"
values="10;1"
></animate>
</circle>
</mask>
<circle
cx="12"
cy="12"
r="10"
mask="url(#lineMdMoonAltToSunnyOutlineLoopTransition0)"
fill="currentColor"
>
<animate
fill="freeze"
attributeName="r"
dur="0.4s"
values="10;6"
></animate>
</circle>
</svg>
</template>
<script lang="ts">
export default {
name: "LineMdMoonAltToSunnyOutlineLoopTransition",
title: "日间主题",
};
</script>

View File

@ -0,0 +1,50 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from Material Line Icons by Vjacheslav Trushkin - https://github.com/cyberalien/line-md/blob/master/license.txt -->
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
>
<path
stroke-dasharray="20"
stroke-dashoffset="20"
d="M12 5c1.66 0 3 1.34 3 3c0 1.66 -1.34 3 -3 3c-1.66 0 -3 -1.34 -3 -3c0 -1.66 1.34 -3 3 -3Z"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
dur="0.4s"
values="20;0"
></animate>
</path>
<path
stroke-dasharray="36"
stroke-dashoffset="36"
d="M12 14c4 0 7 2 7 3v2h-14v-2c0 -1 3 -3 7 -3Z"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.5s"
dur="0.5s"
values="36;0"
></animate>
</path>
</g>
</svg>
</template>
<script lang="ts">
export default {
name: "LineMdPerson",
title: "用户",
};
</script>

View File

@ -0,0 +1,57 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from Material Line Icons by Vjacheslav Trushkin - https://github.com/cyberalien/line-md/blob/master/license.txt -->
<g
fill="currentColor"
fillOpacity="0"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
>
<path
stroke-dasharray="20"
stroke-dashoffset="20"
d="M12 5c1.66 0 3 1.34 3 3c0 1.66 -1.34 3 -3 3c-1.66 0 -3 -1.34 -3 -3c0 -1.66 1.34 -3 3 -3Z"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
dur="0.4s"
values="20;0"
></animate>
</path>
<path
stroke-dasharray="36"
stroke-dashoffset="36"
d="M12 14c4 0 7 2 7 3v2h-14v-2c0 -1 3 -3 7 -3Z"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.5s"
dur="0.5s"
values="36;0"
></animate>
</path>
<animate
fill="freeze"
attributeName="fill-opacity"
begin="1.1s"
dur="0.5s"
values="0;1"
></animate>
</g>
</svg>
</template>
<script lang="ts">
export default {
name: "LineMdPersonFilled",
};
</script>

View File

@ -0,0 +1,59 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from Material Line Icons by Vjacheslav Trushkin - https://github.com/cyberalien/line-md/blob/master/license.txt -->
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
>
<path
fill="currentColor"
fillOpacity="0"
stroke-dasharray="20"
stroke-dashoffset="20"
d="M12 15h2v-6h2.5l-4.5 -4.5M12 15h-2v-6h-2.5l4.5 -4.5"
>
<animate
fill="freeze"
attributeName="fill-opacity"
begin="0.7s"
dur="0.5s"
values="0;1"
></animate>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
dur="0.4s"
values="20;0"
></animate>
</path>
<path
stroke-dasharray="14"
stroke-dashoffset="14"
d="M6 19h12"
>
<animate
fill="freeze"
attributeName="stroke-dashoffset"
begin="0.5s"
dur="0.2s"
values="14;0"
></animate>
</path>
</g>
</svg>
</template>
<script lang="ts">
export default {
name: "LineMdUpload",
title: "文件上传",
};
</script>

View File

@ -0,0 +1,31 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from Lucide by Lucide Contributors - https://github.com/lucide-icons/lucide/blob/main/LICENSE -->
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
>
<circle
cx="12"
cy="12"
r="10"
></circle>
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3m.08 4h.01"></path>
</g>
</svg>
</template>
<script lang="ts">
export default {
name: "LucideCircleHelp",
title: "问题",
};
</script>

View File

@ -0,0 +1,20 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE -->
<path
fill="currentColor"
d="m8.4 17l3.6-3.6l3.6 3.6l1.4-1.4l-3.6-3.6L17 8.4L15.6 7L12 10.6L8.4 7L7 8.4l3.6 3.6L7 15.6zm3.6 5q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8"
></path>
</svg>
</template>
<script lang="ts">
export default {
name: "MaterialSymbolsCancelOutline",
};
</script>

View File

@ -0,0 +1,21 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE -->
<path
fill="currentColor"
d="M3 21v-5h2v3h3v2zm13 0v-2h3v-3h2v5zM3 8V3h5v2H5v3zm16 0V5h-3V3h5v5z"
></path>
</svg>
</template>
<script lang="ts">
export default {
name: "MaterialSymbolsFullscreen",
title: "进入全屏",
};
</script>

View File

@ -0,0 +1,21 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE -->
<path
fill="currentColor"
d="M6 21v-3H3v-2h5v5zm10 0v-5h5v2h-3v3zM3 8V6h3V3h2v5zm13 0V3h2v3h3v2z"
></path>
</svg>
</template>
<script lang="ts">
export default {
name: "MaterialSymbolsFullscreenExit",
title: "退出全屏",
};
</script>

View File

@ -0,0 +1,21 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE -->
<path
fill="currentColor"
d="M4 20v-2h2.75l-.4-.35q-1.225-1.225-1.787-2.662T4 12.05q0-2.775 1.663-4.937T10 4.25v2.1Q8.2 7 7.1 8.563T6 12.05q0 1.125.425 2.188T7.75 16.2l.25.25V14h2v6zm10-.25v-2.1q1.8-.65 2.9-2.212T18 11.95q0-1.125-.425-2.187T16.25 7.8L16 7.55V10h-2V4h6v2h-2.75l.4.35q1.225 1.225 1.788 2.663T20 11.95q0 2.775-1.662 4.938T14 19.75"
></path>
</svg>
</template>
<script lang="ts">
export default {
name: "MaterialSymbolsSync",
title: "刷新",
};
</script>

View File

@ -0,0 +1,26 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from MingCute Icon by MingCute Design - https://github.com/Richard9394/MingCute/blob/main/LICENSE -->
<g fill="none">
<path
d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"
></path>
<path
fill="currentColor"
d="M12 3a3 3 0 0 0-1 5.83V11H8a3 3 0 0 0-3 3v1.17a3.001 3.001 0 1 0 2 0V14a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v1.17a3.001 3.001 0 1 0 2 0V14a3 3 0 0 0-3-3h-3V8.83A3.001 3.001 0 0 0 12 3"
></path>
</g>
</svg>
</template>
<script lang="ts">
export default {
name: "MingcuteDepartmentFill",
title: "部门管理",
};
</script>

View File

@ -0,0 +1,29 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<!-- Icon from MingCute Icon by MingCute Design - https://github.com/Richard9394/MingCute/blob/main/LICENSE -->
<g
fill="none"
fillRule="evenodd"
>
<path
d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"
></path>
<path
fill="currentColor"
d="M15 6a3 3 0 0 1-2 2.83V11h3a3 3 0 0 1 3 3v1.17a3.001 3.001 0 1 1-2 0V14a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v1.17a3.001 3.001 0 1 1-2 0V14a3 3 0 0 1 3-3h3V8.83A3.001 3.001 0 1 1 15 6m-3-1a1 1 0 1 0 0 2a1 1 0 0 0 0-2M6 17a1 1 0 1 0 0 2a1 1 0 0 0 0-2m12 0a1 1 0 1 0 0 2a1 1 0 0 0 0-2"
></path>
</g>
</svg>
</template>
<script lang="ts">
export default {
name: "MingcuteDepartmentLine",
title: "部门管理",
};
</script>

View File

@ -0,0 +1,21 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
>
<!-- Icon from Phosphor by Phosphor Icons - https://github.com/phosphor-icons/core/blob/main/LICENSE -->
<path
fill="currentColor"
d="M208 20h-40a12 12 0 0 0 0 24h11l-15.64 15.67A68 68 0 1 0 108 178.92V192H88a12 12 0 0 0 0 24h20v16a12 12 0 0 0 24 0v-16h20a12 12 0 0 0 0-24h-20v-13.08a67.93 67.93 0 0 0 46.9-100.84L196 61v11a12 12 0 0 0 24 0V32a12 12 0 0 0-12-12m-88 136a44 44 0 1 1 44-44a44.05 44.05 0 0 1-44 44"
></path>
</svg>
</template>
<script lang="ts">
export default {
name: "PhGenderIntersexBold",
title: "性别",
};
</script>

View File

@ -0,0 +1,19 @@
<template>
<svg
class="inline-block"
viewBox="0 0 24 24"
width="1em"
height="1em"
>
<path
fill="currentColor"
d="M21 3v6h-2V6.41l-3.29 3.3l-1.42-1.42L17.59 5H15V3zM3 3v6h2V6.41l3.29 3.3l1.42-1.42L6.41 5H9V3zm18 18v-6h-2v2.59l-3.29-3.29l-1.41 1.41L17.59 19H15v2zM9 21v-2H6.41l3.29-3.29l-1.41-1.42L5 17.59V15H3v6z"
></path>
</svg>
</template>
<script lang="ts">
export default {
name: "test",
};
</script>

View File

@ -0,0 +1,9 @@
import type { CSSProperties } from "vue";
export interface IconProps {
name: string;
size?: number | string;
color?: string;
class?: string;
style?: CSSProperties | Record<string, string>;
}

View File

@ -0,0 +1,32 @@
import { NFlex, selectProps, type SelectRenderLabel } from "naive-ui";
import type { PropType } from "vue";
import { Icon } from "..";
import type { SelectBaseOption } from "naive-ui/es/select/src/interface";
import { iconMap } from "./icon";
export const selectIconProps = {
...selectProps,
clearable: {
type: Boolean,
default: true,
},
renderLabel: {
type: Function as PropType<SelectRenderLabel>,
default: (option: SelectBaseOption) => {
return (
<NFlex class={"margin-xs"}>
<Icon name={option.value as string} />
--<label>{option.label}</label>
</NFlex>
);
},
},
options: {
type: Array as PropType<SelectBaseOption[]>,
default: () =>
Array.from(iconMap.entries()).map(([k, v]) => ({
label: v.title || k,
value: k,
})),
},
};

5
src/components/index.ts Normal file
View File

@ -0,0 +1,5 @@
export * from "./layout";
export * from "./formPro";
export * from "./tablePro";
export * from "./icon";
export * from "./tabsPro";

View File

@ -0,0 +1,50 @@
<template>
<!-- fill="#1e5efd" -->
<svg
class="inline-block text-35px text-primary"
width="30px"
height="30px"
version="1.0"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640.000000 640.000000"
preserveAspectRatio="xMidYMid meet"
>
<g
transform="translate(0.000000,640.000000) scale(0.100000,-0.100000)"
fill="#18a058"
stroke="none"
>
<path
d="M3045 5731 c-138 -23 -319 -101 -427 -185 -75 -59 -157 -148 -198
-215 -14 -23 -56 -90 -94 -149 -38 -59 -79 -123 -91 -142 -59 -94 -128 -200
-158 -242 -17 -25 -45 -70 -62 -98 -16 -28 -44 -70 -61 -93 -17 -23 -39 -58
-49 -77 -9 -19 -24 -43 -33 -53 -8 -9 -40 -57 -71 -106 -87 -137 -159 -249
-356 -551 -59 -91 -117 -181 -130 -200 -12 -19 -53 -82 -91 -140 -38 -58 -71
-109 -73 -115 -2 -5 -50 -79 -105 -165 -56 -85 -135 -207 -176 -270 -102 -159
-165 -257 -245 -380 -37 -58 -82 -127 -100 -155 -18 -27 -75 -115 -127 -195
-53 -80 -101 -156 -108 -170 -7 -14 -26 -41 -42 -60 -15 -19 -28 -39 -28 -44
0 -5 -11 -24 -25 -42 -14 -18 -25 -35 -25 -38 0 -3 -14 -26 -32 -52 -80 -119
-120 -305 -98 -460 25 -173 117 -335 260 -455 112 -94 205 -141 375 -191 72
-21 99 -23 280 -22 168 0 218 4 315 23 63 13 126 28 140 33 14 5 46 13 72 18
27 5 69 19 95 31 27 12 64 28 83 36 119 49 291 150 390 229 140 110 330 318
415 453 78 124 177 320 169 333 -3 4 -112 8 -242 8 -130 0 -232 4 -226 8 6 4
240 355 519 780 279 424 510 772 513 772 4 0 66 -89 139 -198 72 -108 307
-459 522 -779 215 -320 391 -584 391 -587 0 -3 -106 -6 -236 -6 l-235 0 7 -22
c28 -91 179 -343 277 -463 78 -96 226 -241 307 -303 73 -56 251 -172 263 -172
4 0 41 -16 84 -36 43 -20 96 -45 118 -55 22 -10 69 -25 105 -32 36 -8 119 -27
185 -42 96 -21 156 -27 302 -32 165 -5 190 -4 280 17 205 47 357 129 476 254
32 33 70 81 84 106 15 25 35 55 44 68 10 13 30 60 46 105 26 75 28 94 28 222
-1 127 -3 147 -28 213 -26 71 -73 156 -154 278 -21 31 -42 64 -48 73 -5 9 -37
59 -71 111 -34 52 -70 109 -80 126 -11 17 -44 69 -74 115 -30 47 -59 92 -65
102 -5 9 -26 40 -46 69 -20 29 -67 100 -104 158 -38 58 -81 124 -97 147 -15
23 -28 44 -28 48 0 4 -9 19 -20 33 -22 28 -109 162 -176 270 -21 35 -49 76
-60 90 -12 15 -30 43 -40 62 -11 19 -46 76 -78 125 -264 402 -266 405 -376
578 -44 70 -206 320 -267 413 -7 11 -20 33 -29 49 -10 17 -34 53 -56 82 -21
29 -38 55 -38 58 0 3 -43 71 -95 150 -52 79 -98 152 -102 162 -4 10 -15 27
-26 38 -10 11 -25 33 -33 48 -8 15 -43 70 -77 122 -34 52 -83 127 -109 167
-59 90 -138 180 -192 217 -23 15 -62 42 -87 59 -55 38 -166 85 -264 113 -78
23 -330 34 -420 20z"
></path>
</g>
</svg>
</template>

View File

View File

@ -1,153 +0,0 @@
<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>

View File

@ -1,44 +0,0 @@
<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>

View File

@ -1,50 +0,0 @@
<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>

View File

@ -0,0 +1,155 @@
<template>
<n-layout
has-sider
class="h_screen"
:class="{ dark: settingsStore.isDark }"
>
<n-layout-sider
style="box-shadow: rgba(100, 100, 111, 0.2) 0 7px 29px 0"
:bordered="true"
collapse-mode="width"
:collapsed-width="64"
:width="230"
:collapsed="collapsed"
@update:collapsed="collapsed = !collapsed"
>
<div class="title">
<transition enter-active-class="animate__animated animate__fadeInLeft">
<div
class="flex-center h-f"
:key="+collapsed"
>
<IndexIcon />
<span
style="margin-left: 10px"
v-if="!collapsed"
>
{{ platformName }}
</span>
</div>
</transition>
</div>
<LeftMenu />
</n-layout-sider>
<n-layout class="h-f">
<n-layout-header
class="l-header"
bordered
>
<TopHeader v-model:collapsed="collapsed" />
</n-layout-header>
<TopTabs class="l-tabs" />
<n-layout-content class="l-content">
<router-view v-slot="{ Component, route }">
<transition
name="fade-transform"
mode="out-in"
>
<keep-alive :include="cachedViews">
<component
:is="Component"
:key="route.fullPath"
v-if="isRouterShow"
/>
</keep-alive>
</transition>
</router-view>
</n-layout-content>
<n-layout-footer
bordered
position="absolute"
class="l-footer"
:style="{ background: settingsStore.isDark ? '#1e1e1e' : '#fff' }"
>
<div>Copyright © {{ dayjs().year() }} 湖南长沪信息科技有限公司</div>
</n-layout-footer>
</n-layout>
</n-layout>
</template>
<script setup lang="ts">
import dayjs from "dayjs";
import { useTabsStore } from "@/stores/tabsStore";
import { ref, computed, nextTick, provide } from "vue";
import { useRoute } from "vue-router";
import LeftMenu from "./components/LeftMenu.vue";
import TopTabs from "./components/TopTabs.vue";
import TopHeader from "./components/TopHeader.vue";
import { useSettingsStore } from "@/stores/settings";
const settingsStore = useSettingsStore();
// import { useThemeVars } from "naive-ui";
// console.log(useThemeVars);
const tabStore = useTabsStore();
const route = useRoute();
const collapsed = ref(false);
const platformName = "保安可视化管理系统";
//
const cachedViews = computed(() =>
tabStore.tabList
.map((e) => (e.isKeepAlive ? e.name : null))
.filter((name): name is string => Boolean(name))
);
const isRouterShow = ref(true);
const refreshCurrentPage = () => {
setTimeout(() => {
tabStore.changeTabKeepAlive(route.fullPath);
isRouterShow.value = false;
nextTick(() => {
tabStore.changeTabKeepAlive(route.fullPath);
isRouterShow.value = true;
});
}, 0);
};
// v-if
provide("refreshCurrentPage", refreshCurrentPage);
</script>
<style scoped lang="scss">
.h_screen {
width: 100vw;
height: 100vh;
.title {
width: 100%;
height: 50px;
font-size: 18px;
font-weight: bold;
}
.logo {
padding: 10px;
}
.l-header {
height: 50px;
}
.l-tabs {
height: 35px;
}
.l-content {
height: calc(100% - 85px);
padding: 4px 4px 10px;
min-height: 280px;
background-color: #f7fafc;
}
.l-footer {
height: 50px;
margin-top: 10px;
display: flex;
justify-content: center;
align-items: center;
/* background: white; */
}
}
.dark {
.l-content {
background-color: #1e1e1e; //
}
}
</style>

View File

@ -0,0 +1,52 @@
<template>
<n-menu
:value="route.fullPath"
:options="menuOptions"
></n-menu>
</template>
<script setup lang="tsx">
import { Icon } from "@/components";
import { INDEX_ROUTER, USER_CENTER } from "@/config/constant";
import { type AuthMenuTree, usePermissionStore } from "@/stores/permission";
import { isEmpty } from "lodash-es";
import type { MenuOption } from "naive-ui";
import { storeToRefs } from "pinia";
import { computed } from "vue";
import { RouterLink, useRoute } from "vue-router";
const route = useRoute();
const { authMenuList } = storeToRefs(usePermissionStore());
const convertMenuOptions = (data: AuthMenuTree[]): MenuOption[] => {
return data.map((item) => {
const opt: MenuOption = {
label: () =>
item.type.value === "menu" ? (
<RouterLink to={{ path: item.path }}>{item.name}</RouterLink>
) : (
item.name
),
key: item.path,
icon: () => <Icon name={item.icon}></Icon>,
};
if (!isEmpty(item.children)) {
opt.children = convertMenuOptions(item.children as AuthMenuTree[]);
}
return opt;
});
};
const menuOptions = computed<MenuOption[]>(() => {
const menus: MenuOption[] = convertMenuOptions(authMenuList.value);
menus.unshift({
label: () => (
<RouterLink to={{ path: INDEX_ROUTER.path }}>
{INDEX_ROUTER.meta.title}
</RouterLink>
),
key: INDEX_ROUTER.path,
icon: () => <Icon name={INDEX_ROUTER.meta.icon}></Icon>,
});
const menus_ = menus.filter((item) => item.key !== USER_CENTER.path);
return menus_;
});
</script>

View File

@ -0,0 +1,79 @@
<template>
<div class="flex-justify-between h-f margin-left-lg margin-right-lg">
<n-space class="flex-center h-f">
<n-button
quaternary
@click="() => (collapsed = !collapsed)"
>
<template #icon>
<Icon
:name="collapsed ? 'LineMdMenuFoldRight' : 'LineMdMenuFoldLeft'"
/>
</template>
</n-button>
<n-breadcrumb>
<n-breadcrumb-item v-if="route.fullPath === INDEX_ROUTER.path">
<Icon
:name="INDEX_ROUTER?.meta?.icon"
size="18"
>
{{ INDEX_ROUTER?.meta?.title }}
</Icon>
</n-breadcrumb-item>
<n-breadcrumb-item
v-for="item in breadcrumbList"
:key="item.value"
>
<Icon
:name="item?.extData?.icon"
size="18"
>
{{ item.label }}
</Icon>
</n-breadcrumb-item>
</n-breadcrumb>
</n-space>
<n-space>
<FullScreen />
<ToggleTheme />
<UserAvatar />
</n-space>
</div>
</template>
<script setup lang="tsx">
import { INDEX_ROUTER } from "@/config/constant";
import { Icon } from "@/components/icon";
import { usePermissionStore } from "@/stores/permission";
import { computed } from "vue";
import { useRoute, useRouter } from "vue-router";
const permissionStore = usePermissionStore();
import FullScreen from "@/components/fullScreen/index.vue";
import ToggleTheme from "@/components/toggleTheme/index.vue";
import UserAvatar from "@/components/userAvatar/index.vue";
const route = useRoute();
const router = useRouter();
const collapsed = defineModel("collapsed");
const breadcrumbList = computed(() => {
const breadcrumbData =
permissionStore.breadcrumbList[
route.matched[route.matched.length - 1].path
] ?? [];
return !breadcrumbData || breadcrumbData.length === 0 ? [] : breadcrumbData;
});
//todo
const onBreadcrumbClick = (item: SelectNodeVo<string>, index: number) => {
if (index !== breadcrumbList.value.length - 1) {
router.push(item.value);
}
};
</script>
<style lang="scss" scoped>
:deep(.n-breadcrumb-item__link) {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
</style>

View File

@ -0,0 +1,135 @@
<template>
<TabsPro
:value="route.fullPath"
:tab-list="ts"
@update:value="onChange"
@handler-close="handleTabClose"
>
<template #actions>
<n-dropdown
trigger="click"
:options="suffixOptions"
show-arrow
@select="dropdownSelect"
>
<n-button
class="margin-right"
size="small"
>
更多
<IcBaselineKeyboardArrowDown />
</n-button>
</n-dropdown>
</template>
</TabsPro>
</template>
<script setup lang="ts">
import IcBaselineKeyboardArrowDown from "@/components/icon/src/icons/IcBaselineKeyboardArrowDown.vue";
import CarbonSubtractAlt from "@/components/icon/src/icons/CarbonSubtractAlt.vue";
import CodiconCloseAll from "@/components/icon/src/icons/CodiconCloseAll.vue";
import IconamoonSynchronize from "@/components/icon/src/icons/IconamoonSynchronize.vue";
import MaterialSymbolsCancelOutline from "@/components/icon/src/icons/MaterialSymbolsCancelOutline.vue";
import { INDEX_ROUTER } from "@/config/constant";
import { type TabsMenuProps, useTabsStore } from "@/stores/tabsStore";
import { message } from "@/utils";
import { storeToRefs } from "pinia";
import { computed, inject, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { TabsPro } from "@/components";
const route = useRoute();
const router = useRouter();
const tabStore = useTabsStore();
const { tabList } = storeToRefs(useTabsStore());
const ts = computed(() =>
tabList.value.map((e) => ({
value: e.path,
label: e.title,
icon: e.icon,
closable: e.closable,
}))
);
const refreshCurrentPage = inject<Function>("refreshCurrentPage");
//退/
watch(
() => route.fullPath,
() => {
//tab
if (route.meta.isFull) return;
const tabItem: TabsMenuProps = {
title: route.meta.title,
name: route.name as string,
path: route.fullPath,
icon: route.meta.icon as string,
isKeepAlive: route.meta.isKeepAlive,
closable: !route.meta.isFixed,
};
//tab
tabStore.addTab(tabItem);
},
{
immediate: true,
}
);
const onChange = (fullPath: string) => {
router.push(fullPath);
};
const handleTabClose = (val: string) => {
tabStore.closeTab(val);
};
const suffixOptions = [
{
key: "refreshCurrent",
label: "刷新当前",
icon: () => h(IconamoonSynchronize),
},
{
key: "closeCurrent",
label: "关闭当前",
icon: () => h(CarbonSubtractAlt),
},
{
key: "closeOther",
label: "关闭其他",
icon: () => h(MaterialSymbolsCancelOutline),
},
{
key: "closeAll",
label: "关闭所有",
icon: () => h(CodiconCloseAll),
},
];
const dropdownSelect = (key: string) => {
switch (key) {
case "refreshCurrent":
refreshCurrentPage?.();
break;
case "closeCurrent":
if (route.meta.isFixed) {
message.warning("当前为固定tab不可关闭");
return;
}
tabStore.closeTab(route.fullPath);
break;
case "closeOther":
tabStore.closeMultipleTab(route.fullPath);
break;
case "closeAll":
tabStore.closeMultipleTab();
router.push(INDEX_ROUTER.path);
break;
}
};
</script>

View File

@ -1,147 +0,0 @@
<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>

View File

@ -0,0 +1,65 @@
<template>
<TablePro ref="tableRef" :request-api="reqApi" :search-form-options="searchFormOptions" :columns="columns" :isPageTable="false" :pagination="pa" :remote="false">
<template #selectionExtra>
<n-button size="small" text @click="logRows">123</n-button>
</template>
</TablePro>
</template>
<script setup lang="tsx">
import api from '@/axios'
import type { TableProInst, TableProProps } from '@/components/tablePro/src/interface'
import TablePro from '@/components/tablePro/src/TablePro.vue'
import { reactive, ref, useTemplateRef } from 'vue'
type TableType = TableProProps<any, any>
const tableRef = useTemplateRef<TableProInst>('tableRef')
const logRows = () => {
console.log(tableRef.value)
console.log(tableRef.value?.selectRows)
console.log(tableRef.value?.selectKeys)
}
const reqApi: TableType['requestApi'] = (params) => {
const pa = {
params: params,
page: {
current: 1,
size: 100,
},
}
return api.post('/common/pager', pa)
}
const pa = reactive({
page: 1,
pageSize: 10,
onUpdatePage: (n: number) => {
pa.page = n
},
onUpdatePageSize: (s: number) => {
pa.pageSize = s
},
})
const searchFormOptions = reactive<TableType['searchFormOptions']>({
projectName: {
type: 'input',
label: '名字',
},
})
const columns = ref<TableType['columns']>([
{
type: 'selection',
},
{
key: 'name',
title: '名字',
},
{
key: 'address',
title: '地址',
},
])
</script>
<style scoped></style>

View File

@ -0,0 +1,53 @@
<template>
<TablePro
ref="tableRef"
:request-api="reqApi"
:search-form-options="searchFormOptions"
:columns="columns"
>
<template #selectionExtra>
<n-button
size="small"
text
@click="logRows"
>
123
</n-button>
</template>
</TablePro>
</template>
<script setup lang="tsx">
import api from "@/axios";
import type {
TableProInst,
TableProProps,
} from "@/components/tablePro/src/interface";
import TablePro from "@/components/tablePro/src/TablePro.vue";
import { reactive, ref, useTemplateRef } from "vue";
type TableType = TableProProps<any, any>;
const tableRef = useTemplateRef<TableProInst>("tableRef");
const logRows = () => {
console.log(tableRef.value);
console.log(tableRef.value?.selectRows);
console.log(tableRef.value?.selectKeys);
};
const reqApi: TableType["requestApi"] = (params) => {
return api.post("/common/pager", params);
};
const searchFormOptions = reactive<TableType["searchFormOptions"]>({
projectName: {
type: "input",
label: "名字",
},
});
const columns = ref<TableType["columns"]>([
{
key: "name",
title: "菜单名字",
},
]);
</script>
<style scoped></style>

View File

@ -0,0 +1,3 @@
export { tableProProps } from './src/TablePro'
export type { PageParams, Page, PageResult, TableProProps, TableProSlots, TableProInst } from './src/interface'
export { default as TablePro } from './src/TablePro.vue'

Some files were not shown because too many files have changed in this diff Show More