This commit is contained in:
ZhuJW
2026-07-10 18:55:55 +08:00
commit fce40c7d6c
317 changed files with 170079 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
{
"extends": ["@commitlint/config-conventional"],
"rules": {
"type-enum": [
2,
"always",
[
"init",
"build",
"ci",
"chore",
"docs",
"feat",
"fix",
"perf",
"refactor",
"revert",
"style",
"test"
]
]
}
}
+4
View File
@@ -0,0 +1,4 @@
VITE_APP_NAME=Backend
VITE_APP_BASE=/
VITE_APP_BASE_API=/api
VITE_APP_LOAD_ROUTE_WAY=BACKEND
+22
View File
@@ -0,0 +1,22 @@
VITE_APP_BASE_API=/accidentportal/api
VITE_APP_BASE_URL=http://localhost:5221
VITE_APP_LOAD_ROUTE_WAY=FRONTEND
VITE_APP_BASE_API_DEV=/api
VITE_APP_BASE_URL_DEV=http://localhost:5221
# The title of your application (string)
VITE_GLOB_APP_TITLE="RDCA_Incidents Data Portal"
# 标记为测试环境
VITE_ENV=test
# 登录跳转
VITE_LOGIN_URL = http://localhost:5221/accidentportal/user/login
# 请求头的密钥
VITE_APP_CHECK_KEY = 'mbincid-server12'
# 前端配置域名加的前缀名
VITE_APP_DOMAIN_PREFIX='accidentportal'
+14
View File
@@ -0,0 +1,14 @@
VITE_APP_BASE_API=/accidentportal/api
VITE_APP_BASE_URL=http://150.158.236.152:5221
# The title of your application (string)
VITE_GLOB_APP_TITLE="RDCA_Incidents Data Portal"
# 登录跳转
# VITE_LOGIN_URL = http://150.158.236.152:5221/login
VITE_LOGIN_URL = http://150.158.236.152:5221/accidentportal/api/user/login
# 请求头的密钥
VITE_APP_CHECK_KEY = 'mbincid-server12'
# 前端配置域名加的前缀名
VITE_APP_DOMAIN_PREFIX = 'accidentportal'
+5
View File
@@ -0,0 +1,5 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
open_collective: antdv-pro
custom: ['https://docs.antdv-pro.com/other/sponsor.html']
+30
View File
@@ -0,0 +1,30 @@
name: deploy
on:
push:
branches:
- main
jobs:
DEPLOY:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3
with:
node-version: 20.x
cache: pnpm
- name: Install
run: pnpm install --no-frozen-lockfile
- name: Build
run: pnpm run build
- name: Deploy
uses: SamKirkland/FTP-Deploy-Action@v4.3.4
with:
server: ${{ secrets.FTP_HOST }} # e.g. ftp.host.com or sftp.host.com (without ftp:// or ftps://)
username: ${{ secrets.FTP_USERNAME }} # FTP username
password: ${{ secrets.FTP_PASSWORD }} # FTP password
local-dir: dist/ # optional, local path, default is: dist
+20
View File
@@ -0,0 +1,20 @@
name: Issue Close Require
on:
schedule:
- cron: '0 0 * * *'
jobs:
close-issues:
runs-on: ubuntu-latest
steps:
- name: need reproduce
uses: actions-cool/issues-helper@v1.7
with:
actions: close-issues
labels: 🤔 Need Reproduce
inactive-day: 7
body: |
Since the issue was labeled with `Need Reproduce`, but no response in 7 days. This issue will be closed. If you have any questions, you can comment and reply.
由于该 issue 被标记为需要复现信息,却 7 天未收到回应。现关闭 issue,若有任何问题,可评论回复。
+27
View File
@@ -0,0 +1,27 @@
name: Lint
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
LINT:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3
with:
node-version: 20.x
cache: pnpm
- name: Install
run: pnpm install --no-frozen-lockfile
- name: Lint
run: pnpm run lint
+25
View File
@@ -0,0 +1,25 @@
name: Release
permissions:
contents: write
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-node@v3
with:
node-version: 20.x
- run: npx changelogithub # or changelogithub@0.12 if ensure the stable result
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
+3
View File
@@ -0,0 +1,3 @@
echo "husky commit-msg-lint start"
npx --no -- commitlint --edit ""
echo "husky commit-msg-lint end"
+3
View File
@@ -0,0 +1,3 @@
echo "husky lint-staged start"
npx lint-staged
echo "husky lint-staged end"
+4
View File
@@ -0,0 +1,4 @@
public-hoist-pattern[]=@vue/runtime-core
public-hoist-pattern[]=eslint-*
public-hoist-pattern[]=@typescript-eslint*
package-manager-strict=false
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
FROM nginx
RUN rm /etc/nginx/conf.d/default.conf
ADD default.conf /etc/nginx/conf.d/default.conf
COPY dist/ /usr/share/nginx/html/
RUN chmod 775 -R /usr/share/nginx/html
EXPOSE 80/tcp
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) [2023] [Antdv Pro]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+98
View File
@@ -0,0 +1,98 @@
<div align="center"> <a href="https://github.com/antdv-pro/antdv-pro"> <img alt="VbenAdmin Logo" width="200" height="200" src="./public/logo.svg"> </a> <br> <br>
<h1>Antdv Pro</h1>
</div>
![gitee](https://gitee.com/antdv-pro/antdv-pro/badge/star.svg)
![github](https://img.shields.io/github/stars/antdv-pro/antdv-pro?style=social)
**English** | [简体中文](./README.zh-CN.md)
## Introduction
AntdvPro is a complete set of enterprise-level mid-backend front-end/design solutions based on Vue3, Vite4, ant-design-vue4, Pinia, UnoCSS and Typescript. It refers to the design pattern of Ali react version antd-pro, using the latest and most popular The front-end technology stack has built-in dynamic routing, multi-theme, multi-layout and other functions, which can help you quickly build enterprise-level mid-background product prototypes.
## Features
* pnpm: Using the latest pnpm as a package management tool, it can greatly reduce the size of node_modules, speed up the installation speed of packages, and can also share dependencies to reduce disk usage.
* vite: vite as a front-end development tool, it can greatly speed up the start-up speed of the project, and also supports hot updates, which can greatly improve development efficiency.
* vue3: vue3.3.x as the front-end framework, the basic code is written in script-setup, with less code and low maintenance cost.
* nitro mock: Use nitro as the server's mock data, decoupled from the project, and more flexible and easy to use.
* ant-design-vue4: ant-design-vue4 as the UI framework, the author of admin-pro is also a core member of ant-design-vue, which can provide long-term maintenance support.
* pinia: pinia as a state management tool, it can greatly improve the readability and maintainability of the code, and also supports Typescript.
* UnoCSS: Atomic CSS framework, reduce the troubles caused by thinking about some common class names, and improve our development efficiency.
* Code specification: We have encapsulated a set of eslint-based code specification configuration files, which can be used out of the box to unify the problems brought by different teams.
* Theme: The design specifications of antd-pro of the react version are used, and a set of theme modes based on vue are developed. On this basis, some new functions are added to meet various different needs as much as possible.
* Request function: Based on axios, a set of request functions with complete types and some basic interceptor encapsulations are encapsulated. You only need to make corresponding implementation adjustments according to the requirements to meet the different needs of various projects.
* Mobile compatibility: We have tried our best to make the basic framework compatible with the mobile terminal mode, but because our main goal is the enterprise-level mid-background product, we have not made too much adaptation to the mobile terminal. If your project needs to adapt to the mobile terminal, you can refer to our code for corresponding adjustments.
## Preview
[antdv-pro](https://antdv-pro.com) - Test Account: admin/admin
[antdv-pro-js](https://github.com/antdv-pro/antdv-pro/tree/feat-js) - Javascript version
[antdv-pro-js-gitee](https://gitee.com/antdv-pro/antdv-pro/tree/feat-js/) - Gitee Javascript version
[antdv-pro-docs](https://docs.antdv-pro.com) - Documentation
## Community
QQ Group: apply wechat group
Wechat: aibayanyu2022
Wechat Group: Add author WeChat
Discord: [discord](https://discord.gg/tPb4G6gXmm)
## Useage
```bash
# Install degit
npm i -g degit
# Pull the code
degit antdv-pro/antdv-pro [your project name]
# Switch to the project directory
cd [your project name]
# Install
pnpm install
# Development
pnpm dev
```
## Contribute
We are very welcome to have you participate in our open source project.
**Pull Request:**
1. Fork code!
2. Create your own branch: `git checkout -b feat-xxxx`
3. Submit your changes: `git commit -am 'feat(function): add xxxxx'`
4. Push your branch: `git push origin feat-xxxx`
5. submit`pull request`
Thank you to all the people who already contributed to antdv-pro!
<a href="https://github.com/antdv-pro/antdv-pro/graphs/contributors">
<img src="https://contrib.rocks/image?repo=antdv-pro/antdv-pro&max=100&columns=15" />
</a>
## Support
If you like our project, you can support us by clicking the "Star" button in the upper right corner. Your support is my motivation. Thank you ~
Thanks to the open source project license provided by [Jetbrains](https://www.jetbrains.com/?from=antdv-pro).
## Sponsor
If you like our project, you can sponsor us to help us maintain the project better.
[Alipay/Wechat](https://docs.antdv-pro.com/other/sponsor.html)
+97
View File
@@ -0,0 +1,97 @@
<div align="center"> <a href="https://github.com/antdv-pro/antdv-pro"> <img alt="VbenAdmin Logo" width="200" height="200" src="./public/logo.svg"> </a> <br> <br>
<h1>Antdv Pro</h1>
</div>
![gitee](https://gitee.com/antdv-pro/antdv-pro/badge/star.svg)
![github](https://img.shields.io/github/stars/antdv-pro/antdv-pro?style=social)
[English](./README.md) | **简体中文**
## 介绍
AntdvPro是一个基于Vue3、Vite4、ant-design-vue4、Pinia、UnoCSS和Typescript的一整套企业级中后台前端/设计解决方案,它参考了阿里react版本antd-pro的设计模式,使用了最新最流行的前端技术栈,内置了动态路由、多主题、多布局等功能,可以帮助你快速搭建企业级中后台产品原型。
## 特性
* pnpm:使用了最新的pnpm作为包管理工具,它可以大大减少node_modules的体积,加快包的安装速度,同时还可以共享依赖,减少磁盘占用。
* vite:vite作为前端开发工具,它可以大大加快项目的启动速度,同时还支持热更新,可以大大提高开发效率。
* vue3:vue3.3.x作为前端框架,基础代码全部使用script-setup的写法,代码量少维护成本低。
* nitro mock:采用nitro作为服务端的mock数据,从工程中解耦处理,更加灵活易用。
* ant-design-vue4ant-design-vue4作为UI框架,admin-pro的作者也是ant-design-vue的核心成员,可提供长期的维护支持。
* pinia:pinia作为状态管理工具,它可以大大提高代码的可读性和可维护性,同时还支持Typescript。
* UnoCSS:原子化的CSS框架,减少我们去想一些通用类名带来的烦恼,提升我们的开发效率。
* 代码规范:我们封装了一套基于eslint的代码规范配置文件,开箱即用,统一不同团队所带来的问题。
* 主题:延用了react版本的antd-pro的设计规范,开发了一套基于vue的主题模式,在此基础上增加了一些新的功能,尽可能的满足各种不同的需求。
* 请求函数:基于axios封装了一套具有完善类型的请求函数,以及一些基础的拦截器的封装,只需要按照需求做对应的实现调整就能满足各种项目带来的不一样的需求。
* 移动端兼容:基础框架部分我们尽可能的对移动端的模式进行了兼容处理,但是由于我们的主要目标是企业级中后台产品,所以我们并没有对移动端做过多的适配,如果你的项目需要移动端的适配,可以参考我们的代码进行相应的调整。
## 演示
[antdv-pro](https://antdv-pro.com) - 测试账号:admin/admin
[antdv-pro-js](https://github.com/antdv-pro/antdv-pro/tree/feat-js) - JS版本
[antdv-pro-js-gitee](https://gitee.com/antdv-pro/antdv-pro/tree/feat-js/) - Gitee JS 版本
[antdv-pro-docs](https://docs.antdv-pro.com) - 在线文档地址
## 社区
QQ群: 申请微信群
微信: 已经被广告闹麻了自己控制台 Console解密 atob("YWliYXlhbnl1MjAyMg==") 再有广告就对不起大家了,gitee删库不做gitee仓库了
Discord: [discord](https://discord.gg/tPb4G6gXmm)
## 使用
```bash
# 安装degit
npm i -g degit
# 拉取代码
degit antdv-pro/antdv-pro [your project name]
# 切换到项目目录
cd [your project name]
# 安装依赖
pnpm install
# 启动项目
pnpm dev
```
## 贡献
非常欢迎您参与到我们的开源项目中来~
**PR流程:**
1. Fork 代码!
2. 创建自己的分支: `git checkout -b feat-xxxx`
3. 提交你的修改: `git commit -am 'feat(function): add xxxxx'`
4. 推送您的分支: `git push origin feat-xxxx`
5. 提交`pull request`
感谢所有为`antdv-pro`做出贡献的小伙伴儿们!
<a href="https://github.com/antdv-pro/antdv-pro/graphs/contributors">
<img src="https://contrib.rocks/image?repo=antdv-pro/antdv-pro&max=100&columns=15" />
</a>
## 支持
如果你觉得这个项目对你有帮助,你可以点右上角 "Star" 支持一下,你的支持就是我的动力,谢谢~
感谢[Jetbrains](https://www.jetbrains.com/?from=antdv-pro).提供的开源项目许可证支持
## 赞助
如果你觉得这个项目对你有帮助,你可以点击下方链接对我进行赞助,谢谢~
[赞助](https://docs.antdv-pro.com/other/sponsor.html)
+45
View File
@@ -0,0 +1,45 @@
server {
listen 80;
server_name 150.158.236.152; # 替换为你的服务器 IP/域名
client_max_body_size 100M; # 全局生效的文件上传大小限制
# 日志配置
access_log /var/log/nginx/front-access.log main;
error_log /var/log/nginx/front-error.log warn;
# gzip 压缩(保留原有配置)
gzip on;
gzip_min_length 1k;
gzip_comp_level 9;
gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";
# 前端静态文件根目录(适配 Docker 挂载)
root /usr/share/nginx/html;
include /etc/nginx/mime.types;
# 静态资源缓存优化
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 7d;
add_header Cache-Control "public, max-age=604800";
access_log off;
}
# SPA 路由兜底
location / {
try_files $uri $uri/ /index.html;
}
# 错误页适配 SPA
error_page 404 500 502 503 504 /index.html;
# 后端 API 代理(替换为实际地址)
location /api {
rewrite ^/api(.*)$ $1 break;
proxy_pass http://150.158.236.152:5221; # 你的 Flask 后端地址
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
+21
View File
@@ -0,0 +1,21 @@
import antfu from '@antfu/eslint-config'
export default antfu({
formatters: {
css: true,
},
ignores: [
'types/auto-imports.d.ts',
'types/components.d.ts',
'public',
'tsconfig.*.json',
'tsconfig.json',
],
}, {
rules: {
'no-console': 0,
'style/quote-props': 0,
'unused-imports/no-unused-vars': 0,
'ts/no-unused-expressions': 0,
},
})
+22
View File
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title></title>
<script>
(function () {
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
const setting = localStorage.getItem('vueuse-color-scheme') || 'auto'
if (setting === 'dark' || (prefersDark && setting !== 'light'))
document.documentElement.classList.toggle('dark', true)
})()
</script>
</head>
<body>
<div id="app"></div>
<script src="/loading.js"></script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
+101
View File
@@ -0,0 +1,101 @@
{
"name": "antdv-pro",
"type": "module",
"version": "1.4.0",
"packageManager": "pnpm@10.17.1",
"author": "aibayanyu <aibayanyu@qq.com>",
"repository": "antdv-pro/antdv-pro",
"engines": {
"node": ">=20.15.0"
},
"scripts": {
"dev": "vite --host",
"build": "vite build",
"clear:vercel": "rm -rf ./vercel.json",
"build:vercel": "run-s clear:vercel",
"start": "node dist/servers/app.mjs",
"preview": "vite preview",
"lint": "eslint src --fix",
"typecheck": "vue-tsc --noEmit",
"bump:patch": "changelogen --bump --output CHANGELOG.md --release",
"bump:minor": "changelogen --bump --output CHANGELOG.md --release --minor",
"bump:major": "changelogen --bump --output CHANGELOG.md --release --major",
"prepare": "husky",
"dir-tree": "esno ./scripts/dir-tree",
"gen:uno": "esno ./scripts/gen-unocss",
"toJS": "esno scripts/to-js"
},
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@ant-design/icons-vue": "^7.0.1",
"@antv/g2plot": "^2.4.35",
"@antv/l7": "^2.23.0",
"@ctrl/tinycolor": "^4.2.0",
"@v-c/utils": "^0.0.26",
"@vueuse/core": "^10.11.1",
"ant-design-vue": "^4.2.6",
"axios": "^1.12.2",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.18",
"echarts": "^6.0.0",
"lodash-es": "^4.17.21",
"mitt": "^3.0.1",
"pinia": "^2.3.1",
"sjcl": "^1.0.9",
"sm-crypto": "^0.4.0",
"vue": "^3.5.22",
"vue-i18n": "^9.14.5",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@antfu/eslint-config": "^5.4.1",
"@commitlint/cli": "^18.6.1",
"@commitlint/config-conventional": "^18.6.3",
"@mistjs/vite-plugin-preload": "^0.0.1",
"@types/crypto-js": "^4.2.2",
"@types/fs-extra": "^11.0.4",
"@types/lodash": "^4.17.20",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.19.17",
"@types/treeify": "^1.0.3",
"@vitejs/plugin-vue": "^6.0.1",
"@vitejs/plugin-vue-jsx": "^5.1.1",
"@vue/test-utils": "^2.4.6",
"antdv-component-resolver": "^1.0.8",
"antdv-style": "0.0.1-beta.4",
"changelogen": "^0.5.7",
"cross-env": "^7.0.3",
"directory-tree": "^3.5.2",
"esbuild": "^0.25.10",
"eslint": "^9.36.0",
"eslint-plugin-format": "^1.0.2",
"esno": "^0.17.0",
"execa": "^8.0.1",
"fs-extra": "^11.3.2",
"h3": "2.0.0-beta.4",
"husky": "^9.1.7",
"jiti": "^2.6.0",
"jsdom": "^22.1.0",
"less": "^4.4.1",
"lint-staged": "^14.0.1",
"lodash": "^4.17.21",
"mock-h3": "0.0.1-beta.12",
"npm-run-all": "^4.1.5",
"picocolors": "^1.1.1",
"treeify": "^1.1.0",
"ts-node": "^10.9.2",
"typescript": "~5.8.3",
"unocss": "^66.5.2",
"unocss-preset-chinese": "^0.3.3",
"unocss-preset-ease": "^0.0.3",
"unplugin-auto-import": "^19.3.0",
"unplugin-config": "^0.1.5",
"unplugin-vue-components": "^29.1.0",
"vite": "npm:rolldown-vite@7.1.13",
"vitest": "^3.2.4",
"vue-tsc": "^3.0.8"
},
"lint-staged": {
"**/*.{vue,ts,js,jsx,tsx}": "eslint src --fix"
}
}
+5
View File
@@ -0,0 +1,5 @@
// This constant defines the name of the configuration file that will be used in the production environment
export const GLOB_CONFIG_FILE_NAME = '_app.config.js'
// This constant sets the output directory for the Vite package
export const OUTPUT_DIR = 'dist'
+52
View File
@@ -0,0 +1,52 @@
import type { PluginOption } from 'vite'
import VitePluginPreloadAll from '@mistjs/vite-plugin-preload'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import AntdvResolver from 'antdv-component-resolver'
import { mockH3 } from 'mock-h3/vite'
import Unocss from 'unocss/vite'
import AutoImport from 'unplugin-auto-import/vite'
import GenerateConfig from 'unplugin-config/vite'
import Components from 'unplugin-vue-components/vite'
import { GLOB_CONFIG_FILE_NAME, OUTPUT_DIR } from './constants'
import { viteBuildInfo } from './vite-build-info'
export function createVitePlugins(env: Record<string, string>) {
const vitePluginList: (PluginOption | PluginOption[])[] = [
vue(),
vueJsx(),
// mockH3({}),
VitePluginPreloadAll(),
AutoImport({
imports: [
'vue',
'vue-router',
'vue-i18n',
'@vueuse/core',
'pinia',
],
dts: 'types/auto-imports.d.ts',
dirs: ['src/stores', 'src/composables'],
}),
Components({
resolvers: [AntdvResolver()],
dts: 'types/components.d.ts',
dirs: ['src/components'],
}),
// https://github.com/kirklin/unplugin-config
GenerateConfig({
appName: env.VITE_GLOB_APP_TITLE,
configFile: {
generate: true,
fileName: GLOB_CONFIG_FILE_NAME,
outputDir: OUTPUT_DIR,
},
envVariables: {
prefix: 'VITE_GLOB_',
},
}),
Unocss(),
viteBuildInfo(env.VITE_APP_NAME),
]
return vitePluginList
}
+207
View File
@@ -0,0 +1,207 @@
import type { Dayjs } from 'dayjs'
import type { Plugin, ResolvedConfig } from 'vite'
import { readdir, stat } from 'node:fs'
import dayjs from 'dayjs'
import duration from 'dayjs/plugin/duration'
import pkg from 'picocolors'
const { green, blue, bold } = pkg
dayjs.extend(duration)
const fileListTotal: number[] = []
// eslint-disable-next-line ts/no-unsafe-function-type
function recursiveDirectory(folder: string, callback: Function): void {
readdir(folder, (err, files: string[]) => {
if (err)
throw err
let count = 0
const checkEnd = () => {
++count === files.length && callback()
}
files.forEach((item: string) => {
stat(`${folder}/${item}`, async (err, stats) => {
if (err)
throw err
if (stats.isFile()) {
fileListTotal.push(stats.size)
checkEnd()
}
else if (stats.isDirectory()) {
recursiveDirectory(`${folder}/${item}/`, checkEnd)
}
})
})
files.length === 0 && callback()
})
}
function sum(arr: number[]) {
return arr.reduce((t: number, c: number) => {
return t + c
}, 0)
}
function formatBytes(a: number, b?: number): string {
if (a === 0)
return '0 Bytes'
const c = 1024
const d = b || 2
const e = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const f = Math.floor(Math.log(a) / Math.log(c))
return `${Number.parseFloat((a / c ** f).toFixed(d))} ${e[f]}`
}
export function viteBuildInfo(name: string): Plugin {
let config: ResolvedConfig
let startTime: Dayjs
let endTime: Dayjs
return {
name: 'vite:buildInfo',
configResolved(resolvedConfig) {
config = resolvedConfig
},
buildStart() {
console.log(
bold(
green(
`👏欢迎使用${blue(`[${name}]`)},现在正全力为您${config.command === 'build' ? '打包' : '编译'
}`,
),
),
)
if (config.command === 'build')
startTime = dayjs(new Date())
},
closeBundle() {
if (config.command === 'build') {
endTime = dayjs(new Date())
recursiveDirectory(config.build.outDir, () => {
console.log(
bold(
green(
`恭喜打包完成🎉(总用时${dayjs
.duration(endTime.diff(startTime))
.format('mm分ss秒')},打包后的大小为${formatBytes(
sum(fileListTotal),
)}`,
),
),
)
})
}
},
}
}
// import type { Dayjs } from 'dayjs'
// import type { Plugin, ResolvedConfig } from 'vite'
// import { readdir, stat, existsSync } from 'node:fs' // 👈 新增 existsSync
// import { resolve } from 'node:path' // 👈 新增 resolve
// import dayjs from 'dayjs'
// import duration from 'dayjs/plugin/duration'
// import pkg from 'picocolors'
// const { green, blue, bold } = pkg
// dayjs.extend(duration)
// const fileListTotal: number[] = []
// function recursiveDirectory(folder: string, callback: () => void): void {
// // 👇 先检查目录是否存在
// if (!existsSync(folder)) {
// console.warn(`[vite:buildInfo] 目录不存在: ${folder}`)
// callback()
// return
// }
// readdir(folder, (err, files: string[]) => {
// if (err) {
// console.error('[vite:buildInfo] 读取目录失败:', err)
// callback()
// return
// }
// let count = 0
// const checkEnd = () => {
// if (++count === files.length) callback()
// }
// if (files.length === 0) {
// callback()
// return
// }
// files.forEach((item: string) => {
// const fullPath = resolve(folder, item) // 👈 使用 resolve 拼接路径
// stat(fullPath, (err, stats) => {
// if (err) {
// checkEnd()
// return
// }
// if (stats.isFile()) {
// fileListTotal.push(stats.size)
// checkEnd()
// } else if (stats.isDirectory()) {
// recursiveDirectory(fullPath, checkEnd)
// }
// })
// })
// })
// }
// function sum(arr: number[]) {
// return arr.reduce((t, c) => t + c, 0)
// }
// function formatBytes(a: number, b = 2): string {
// if (a === 0) return '0 Bytes'
// const c = 1024
// const e = ['Bytes', 'KB', 'MB', 'GB', 'TB']
// const f = Math.floor(Math.log(a) / Math.log(c))
// return `${parseFloat((a / c ** f).toFixed(b))} ${e[f]}`
// }
// export function viteBuildInfo(name: string): Plugin {
// let config: ResolvedConfig
// let startTime: Dayjs
// let endTime: Dayjs
// return {
// name: 'vite:buildInfo',
// configResolved(resolvedConfig) {
// config = resolvedConfig
// },
// buildStart() {
// console.log(
// bold(
// green(
// `👏欢迎使用${blue(`[${name}]`)},现在正全力为您${config.command === 'build' ? '打包' : '编译'}`,
// ),
// ),
// )
// if (config.command === 'build') {
// startTime = dayjs()
// }
// },
// closeBundle() {
// if (config.command === 'build') {
// endTime = dayjs()
// // 👇 关键:将 outDir 转为绝对路径!
// const outDir = resolve(config.root || process.cwd(), config.build.outDir)
// recursiveDirectory(outDir, () => {
// console.log(
// bold(
// green(
// `恭喜打包完成🎉(总用时${dayjs
// .duration(endTime.diff(startTime))
// .format('mm分ss秒')},打包后的大小为${formatBytes(sum(fileListTotal))}`,
// ),
// ),
// )
// })
// }
// },
// }
// }
+12774
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
onlyBuiltDependencies:
- esbuild
View File
+211
View File
@@ -0,0 +1,211 @@
/**
* loading 占位
* 解决首次加载时白屏的问题
*/
(function () {
const div = document.createElement('div')
const body = document.querySelector('body')
body.appendChild(div)
div.setAttribute('id', 'loading-app')
if (div && div.innerHTML === '') {
div.innerHTML = `
<style>
html,
body,
#loading-app {
height: 100%;
margin: 0;
padding: 0;
}
#loading-app {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
background-repeat: no-repeat;
background-size: 100% auto;
}
.loading-title {
font-size: 1.1rem;
}
.loading-sub-title {
margin-top: 20px;
font-size: 1rem;
color: #888;
}
.page-loading-warp {
display: flex;
align-items: center;
justify-content: center;
padding: 26px;
}
.ant-spin {
position: absolute;
display: none;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
padding: 0;
color: rgba(0, 0, 0, 0.65);
color: #1890ff;
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5;
text-align: center;
list-style: none;
opacity: 0;
-webkit-transition: -webkit-transform 0.3s
cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: -webkit-transform 0.3s
cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86),
-webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
-webkit-font-feature-settings: "tnum";
font-feature-settings: "tnum";
}
.ant-spin-spinning {
position: static;
display: inline-block;
opacity: 1;
}
.ant-spin-dot {
position: relative;
display: inline-block;
width: 20px;
height: 20px;
font-size: 20px;
}
.ant-spin-dot-item {
position: absolute;
display: block;
width: 9px;
height: 9px;
background-color: #1890ff;
border-radius: 100%;
-webkit-transform: scale(0.75);
-ms-transform: scale(0.75);
transform: scale(0.75);
-webkit-transform-origin: 50% 50%;
-ms-transform-origin: 50% 50%;
transform-origin: 50% 50%;
opacity: 0.3;
-webkit-animation: ant-spin-move 1s infinite linear alternate;
animation: ant-spin-move 1s infinite linear alternate;
}
.ant-spin-dot-item:nth-child(1) {
top: 0;
left: 0;
}
.ant-spin-dot-item:nth-child(2) {
top: 0;
right: 0;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
.ant-spin-dot-item:nth-child(3) {
right: 0;
bottom: 0;
-webkit-animation-delay: 0.8s;
animation-delay: 0.8s;
}
.ant-spin-dot-item:nth-child(4) {
bottom: 0;
left: 0;
-webkit-animation-delay: 1.2s;
animation-delay: 1.2s;
}
.ant-spin-dot-spin {
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
-webkit-animation: ant-rotate 1.2s infinite linear;
animation: ant-rotate 1.2s infinite linear;
}
.ant-spin-lg .ant-spin-dot {
width: 32px;
height: 32px;
font-size: 32px;
}
.ant-spin-lg .ant-spin-dot i {
width: 14px;
height: 14px;
}
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
.ant-spin-blur {
background: #fff;
opacity: 0.5;
}
}
@-webkit-keyframes ant-spin-move {
to {
opacity: 1;
}
}
@keyframes ant-spin-move {
to {
opacity: 1;
}
}
@-webkit-keyframes ant-rotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
@keyframes ant-rotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
</style>
<div style="
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
min-height: 362px;
">
<div class="page-loading-warp">
<div class="ant-spin ant-spin-lg ant-spin-spinning">
<span class="ant-spin-dot ant-spin-dot-spin">
<i class="ant-spin-dot-item"></i>
<i class="ant-spin-dot-item"></i>
<i class="ant-spin-dot-item"></i>
<i class="ant-spin-dot-item"></i>
</span>
</div>
</div>
<div class="loading-title">
正在加载资源
</div>
<div class="loading-sub-title">
初次加载资源可能需要较多时间 请耐心等待
</div>
</div>
`
}
})()
Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

+23
View File
@@ -0,0 +1,23 @@
import dirTree from 'directory-tree'
import treeify from 'treeify'
// eslint-disable-next-line node/prefer-global/process
const filteredTree = dirTree(process.cwd(), {
exclude: [/node_modules/, /\.git/, /\.vscode/, /\.idea/],
})
const children = filteredTree.children ?? []
function genObj(children: any[]) {
const obj: Record<string, any> = {}
for (const child of children)
obj[child.name] = (child?.children && child.children.length > 0) ? genObj(child.children) : null
return obj
}
const obj = genObj(children)
const md = treeify.asTree(obj, true, null)
console.log(md)
+35
View File
@@ -0,0 +1,35 @@
import path from 'node:path'
import { theme } from 'ant-design-vue'
import fsExtra from 'fs-extra'
import lodash from 'lodash'
const { defaultAlgorithm, defaultSeed } = theme
const mapToken = defaultAlgorithm(defaultSeed)
function formatKey(key: string, prefixCls: string) {
return `${prefixCls}${lodash.kebabCase(key)}`
}
const prefixCls = '--pro-ant-'
const variables: {
colors: Record<string, any>
} = {
colors: {},
}
let colorTheme = ''
for (const key in mapToken) {
if (key.startsWith('color')) {
const cssVar = formatKey(key, prefixCls)
const colors = variables.colors
const themeKey = lodash.camelCase(key.slice(5))
colors[themeKey] = `var(${cssVar})`
colorTheme += `${themeKey}\n`
}
}
// eslint-disable-next-line node/prefer-global/process
fsExtra.outputFile(path.resolve(process.cwd(), './themes/antd-uno-theme.json'), JSON.stringify(variables, null, 2))
// eslint-disable-next-line node/prefer-global/process
fsExtra.outputFile(path.resolve(process.cwd(), './themes/color-theme-var.md'), colorTheme)
+17
View File
@@ -0,0 +1,17 @@
import { resolve } from 'node:path'
import * as process from 'node:process'
import { transformSync } from 'esbuild'
import fsExtra from 'fs-extra'
const themeConfig = resolve(process.cwd(), './src/config/default-setting.ts')
const code = fsExtra.readFileSync(themeConfig, 'utf-8')
function toJs(code: string) {
const res = transformSync(code, {
target: 'esnext',
loader: 'ts',
})
console.log(res.code)
}
toJs(code)
+10
View File
@@ -0,0 +1,10 @@
import { defineEventHandler } from 'h3'
export default defineEventHandler((event) => {
// setResponseStatus(event, 401)
event.res.status = 401
return {
code: 401,
msg: '请先登录',
}
})
+9
View File
@@ -0,0 +1,9 @@
import { defineEventHandler } from 'h3'
export default defineEventHandler((event) => {
event.res.status = 403
return {
code: 403,
msg: '请先登录',
}
})
+9
View File
@@ -0,0 +1,9 @@
import { defineEventHandler } from 'h3'
export default defineEventHandler((event) => {
event.res.status = 500
return {
code: 500,
msg: '服务器错误',
}
})
+5
View File
@@ -0,0 +1,5 @@
import { defineEventHandler } from 'h3'
export default defineEventHandler(() => {
return { nitro: 'Hello Antdv Pro' }
})
@@ -0,0 +1,16 @@
import { defineEventHandler } from 'h3'
export default defineEventHandler((event) => {
const id = event.context.params?.id as any
if (typeof id !== 'number') {
event.res.status = 403
return {
code: 403,
msg: '删除失败',
}
}
return {
code: 200,
msg: '删除成功',
}
})
@@ -0,0 +1,80 @@
import dayjs from 'dayjs'
import { defineEventHandler } from 'h3'
import { cloneDeep } from 'lodash-es'
export default defineEventHandler(async (_event) => {
const dataList = [
{
title: 'Aipay',
link: 'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png',
percent: 57,
content: '一生那么短,遗忘又那么漫长',
},
{
title: 'Ant Design Vue',
link: 'https://www.antdv.com/assets/logo.1ef800a8.svg',
percent: 60,
status: 'active',
content: '只有在梦想中,人才能真正自由',
},
{
title: 'Vue',
link: 'https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png',
percent: 70,
status: 'exception',
content: '生命就像一盒巧克力,结果往往出人意料',
},
{
title: 'Vite',
link: 'https://cn.vitejs.dev/logo.svg',
percent: 100,
status: 'active',
content: '有时,你必须进入别人的世界去发现自己的世界缺少什么',
},
{
title: 'React',
link: 'https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png',
percent: 50,
status: 'exception',
content: '希望是件美丽的东西,也许是最好的东西',
},
{
title: 'Antdv Pro',
link: '/logo.svg',
percent: 80,
status: 'active',
content: '人并非生来就伟大,而是越活越伟大',
},
{
title: 'Webpack',
link: 'https://gw.alipayobjects.com/zos/rmsportal/nxkuOJlFJuAUhzlMTCEe.png',
percent: 58,
content: '不管何时何地,做你想做的事永远都不嫌晚',
},
{
title: 'Angular',
link: 'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png',
percent: 70,
status: 'active',
content: '你要一直不停地往前走,不然你不会知道生活还会给你什么',
},
]
const data: any[] = []
// 数据复制
for (let i = 0; i < 1000; i++) {
const arr = cloneDeep(dataList)
data.push(...arr)
}
// 配置任务时间
for (let i = 0; i < data.length; i++)
data[i].start = dayjs().subtract(i, 'hour').format('YYYY-MM-DD HH:mm')
return {
code: 200,
msg: '获取成功',
data,
}
})
@@ -0,0 +1,82 @@
import dayjs from 'dayjs'
import { defineEventHandler, readBody } from 'h3'
enum STATUS {
OFF = '0',
RUNNING = '1',
ONLINE = '2',
ERROR = '3',
}
export default defineEventHandler(async (_event) => {
const body = (await readBody(_event)) as { name?: string }
const dataList = [
{
id: 1,
name: '第一个任务',
callNo: 2000,
desc: '一生那么短,遗忘又那么漫长',
status: STATUS.ONLINE,
updatedAt: dayjs().format('YYYY-MM-DD HH:mm'),
},
{
id: 2,
name: 'Ant Design Vue',
callNo: 200,
desc: '有时,你必须进入别人的世界去发现自己的世界缺少什么',
status: STATUS.OFF,
updatedAt: dayjs().format('YYYY-MM-DD HH:mm'),
},
{
id: 3,
name: 'Vue',
callNo: 2010,
desc: '一生那么短,遗忘又那么漫长',
status: STATUS.ERROR,
updatedAt: dayjs().format('YYYY-MM-DD HH:mm'),
},
{
id: 4,
name: 'Vite',
callNo: 20300,
desc: '希望是件美丽的东西,也许是最好的东西',
status: STATUS.ERROR,
updatedAt: dayjs().format('YYYY-MM-DD HH:mm'),
},
{
id: 5,
name: 'React',
callNo: 2000,
desc: '人并非生来就伟大,而是越活越伟大',
status: STATUS.ONLINE,
updatedAt: dayjs().format('YYYY-MM-DD HH:mm'),
},
{
id: 6,
name: 'Antdv Pro',
callNo: 2000,
desc: '不管何时何地,做你想做的事永远都不嫌晚',
status: STATUS.OFF,
updatedAt: dayjs().format('YYYY-MM-DD HH:mm'),
},
{
id: 7,
name: 'Webpack',
callNo: 2000,
desc: '你要一直不停地往前走,不然你不会知道生活还会给你什么',
status: STATUS.ONLINE,
updatedAt: dayjs().format('YYYY-MM-DD HH:mm'),
},
]
const data = dataList.filter((i) => {
if (body.name)
return body.name === i.name
else return true
})
return {
code: 200,
msg: '获取成功',
data,
}
})
@@ -0,0 +1,10 @@
import { defineEventHandler, readBody } from 'h3'
export default defineEventHandler(async (event) => {
const body = await readBody(event)
console.log(body)
return {
code: 200,
msg: '创建成功',
}
})
@@ -0,0 +1,63 @@
import { defineEventHandler, readBody } from 'h3'
export default defineEventHandler(async (_event) => {
const body = await readBody(_event)
const dataList = [
{
id: 1,
name: '第一个任务',
value: '2000',
remark: '一生那么短,遗忘又那么漫长',
},
{
id: 2,
name: 'Ant Design Vue',
value: '200',
remark: '有时,你必须进入别人的世界去发现自己的世界缺少什么',
},
{
id: 3,
name: 'Vue',
value: '2010',
remark: '一生那么短,遗忘又那么漫长',
},
{
id: 4,
name: 'Vite',
value: '20300',
remark: '希望是件美丽的东西,也许是最好的东西',
},
{
id: 5,
name: 'React',
value: '2000',
remark: '人并非生来就伟大,而是越活越伟大',
},
{
id: 6,
name: 'Antdv Pro',
value: '2000',
remark: '不管何时何地,做你想做的事永远都不嫌晚',
},
{
id: 7,
name: 'Webpack',
value: '2000',
remark: '你要一直不停地往前走,不然你不会知道生活还会给你什么',
},
]
const data = dataList.filter((i) => {
if (body.name)
return body.name === i.name
else return true
})
return {
code: 200,
msg: '获取成功',
data: {
records: data,
total: data.length,
},
}
})
@@ -0,0 +1,22 @@
import { defineEventHandler, readBody } from 'h3'
export default defineEventHandler(async (event) => {
const body = await readBody(event)
const dataList = []
for (let i = 0; i < 10; i++) {
const item = {
id: i + 1,
title: `${body.title ?? '测试'}_${i}`,
username: `${body.username ?? 'test'}_${i}`,
password: `${body.username ?? 'test'}_pass_${i}`,
}
dataList.push(item)
}
return {
code: 200,
msg: '获取成功',
data: dataList,
}
})
@@ -0,0 +1,8 @@
import { defineEventHandler } from 'h3'
export default defineEventHandler(async (_event) => {
return {
code: 200,
msg: '编辑成功',
}
})
+33
View File
@@ -0,0 +1,33 @@
import { defineEventHandler, readBody } from 'h3'
export default defineEventHandler(async (event) => {
const body: any = await readBody(event)
const { type } = body
const success = {
code: 200,
data: {
token: '1234567890',
},
msg: '登录成功',
}
if (type !== 'mobile') {
// eslint-disable-next-line node/prefer-global/buffer
success.data.token = Buffer.from(body.username).toString('base64')
// 判断用户名密码是否正确
if (body.username === 'admin' && body.password === 'admin')
return success
if (body.username === 'user' && body.password === 'user')
return success
}
else {
return success
}
// setResponseStatus(event, 403)
event.res.status = 403
return {
code: 401,
msg: '用户名或密码错误',
}
})
+8
View File
@@ -0,0 +1,8 @@
import { defineEventHandler } from 'h3'
export default defineEventHandler(() => {
return {
code: 200,
msg: 'success',
}
})
+474
View File
@@ -0,0 +1,474 @@
import { defineEventHandler } from 'h3'
const menuData = [
{
id: 2,
parentId: null,
title: '分析页',
icon: 'DashboardOutlined',
component: '/dashboard/analysis',
path: '/dashboard/analysis',
name: 'DashboardAnalysis',
keepAlive: true,
locale: 'menu.dashboard.analysis',
},
{
id: 1,
parentId: null,
title: '仪表盘',
icon: 'DashboardOutlined',
component: 'RouteView',
redirect: '/dashboard/analysis',
path: '/dashboard',
name: 'Dashboard',
locale: 'menu.dashboard',
},
{
id: 3,
parentId: null,
title: '表单页',
icon: 'FormOutlined',
component: 'RouteView',
redirect: '/form/basic',
path: '/form',
name: 'Form',
locale: 'menu.form',
},
{
id: 5,
parentId: null,
title: '链接',
icon: 'LinkOutlined',
component: 'RouteView',
redirect: '/link/iframe',
path: '/link',
name: 'Link',
locale: 'menu.link',
},
{
id: 6,
parentId: 5,
title: 'AntDesign',
url: 'https://ant.design/',
component: 'Iframe',
path: '/link/iframe',
name: 'LinkIframe',
keepAlive: true,
locale: 'menu.link.iframe',
},
{
id: 7,
parentId: 5,
title: 'AntDesignVue',
url: 'https://antdv.com/',
component: 'Iframe',
path: '/link/antdv',
name: 'LinkAntdv',
keepAlive: true,
locale: 'menu.link.antdv',
},
{
id: 8,
parentId: 5,
path: 'https://www.baidu.com',
name: 'LinkExternal',
title: '跳转百度',
locale: 'menu.link.external',
},
{
id: 9,
parentId: null,
title: '菜单',
icon: 'BarsOutlined',
component: 'RouteView',
path: '/menu',
redirect: '/menu/menu1',
name: 'Menu',
locale: 'menu.menu',
},
{
id: 10,
parentId: 9,
title: '菜单1',
component: '/menu/menu1',
path: '/menu/menu1',
name: 'MenuMenu11',
keepAlive: true,
locale: 'menu.menu.menu1',
},
{
id: 11,
parentId: 9,
title: '菜单2',
component: '/menu/menu2',
path: '/menu/menu2',
keepAlive: true,
locale: 'menu.menu.menu2',
},
{
id: 12,
parentId: 9,
path: '/menu/menu3',
redirect: '/menu/menu3/menu1',
title: '菜单1-1',
component: 'RouteView',
locale: 'menu.menu.menu3',
},
{
id: 13,
parentId: 12,
path: '/menu/menu3/menu1',
component: '/menu/menu-1-1/menu1',
title: '菜单1-1-1',
keepAlive: true,
locale: 'menu.menu3.menu1',
},
{
id: 14,
parentId: 12,
path: '/menu/menu3/menu2',
component: '/menu/menu-1-1/menu2',
title: '菜单1-1-2',
keepAlive: true,
locale: 'menu.menu3.menu2',
},
{
id: 15,
path: '/access',
component: 'RouteView',
redirect: '/access/common',
title: '权限模块',
name: 'Access',
parentId: null,
icon: 'ClusterOutlined',
locale: 'menu.access',
},
{
id: 16,
parentId: 15,
path: '/access/common',
title: '通用权限',
name: 'AccessCommon',
component: '/access/common',
locale: 'menu.access.common',
},
{
id: 17,
parentId: 15,
path: '/access/user',
title: '普通用户',
name: 'AccessUser',
component: '/access/user',
locale: 'menu.access.user',
},
{
id: 19,
parentId: null,
title: '异常页',
icon: 'WarningOutlined',
component: 'RouteView',
redirect: '/exception/403',
path: '/exception',
name: 'Exception',
locale: 'menu.exception',
},
{
id: 20,
parentId: 19,
path: '/exception/403',
title: '403',
name: '403',
component: '/exception/403',
locale: 'menu.exception.not-permission',
},
{
id: 21,
parentId: 19,
path: '/exception/404',
title: '404',
name: '404',
component: '/exception/404',
locale: 'menu.exception.not-find',
},
{
id: 22,
parentId: 19,
path: '/exception/500',
title: '500',
name: '500',
component: '/exception/500',
locale: 'menu.exception.server-error',
},
{
id: 23,
parentId: null,
title: '结果页',
icon: 'CheckCircleOutlined',
component: 'RouteView',
redirect: '/result/success',
path: '/result',
name: 'Result',
locale: 'menu.result',
},
{
id: 24,
parentId: 23,
path: '/result/success',
title: '成功页',
name: 'ResultSuccess',
component: '/result/success',
locale: 'menu.result.success',
},
{
id: 25,
parentId: 23,
path: '/result/fail',
title: '失败页',
name: 'ResultFail',
component: '/result/fail',
locale: 'menu.result.fail',
},
{
id: 26,
parentId: null,
title: '列表页',
icon: 'TableOutlined',
component: 'RouteView',
redirect: '/list/card-list',
path: '/list',
name: 'List',
locale: 'menu.list',
},
{
id: 27,
parentId: 26,
path: '/list/card-list',
title: '卡片列表',
name: 'ListCard',
component: '/list/card-list',
locale: 'menu.list.card-list',
},
{
id: 28,
parentId: null,
title: '详情页',
icon: 'ProfileOutlined',
component: 'RouteView',
redirect: '/profile/basic',
path: '/profile',
name: 'Profile',
locale: 'menu.profile',
},
{
id: 29,
parentId: 28,
path: '/profile/basic',
title: '基础详情页',
name: 'ProfileBasic',
component: '/profile/basic/index',
locale: 'menu.profile.basic',
},
{
id: 30,
parentId: 26,
path: '/list/search-list',
title: '搜索列表',
name: 'SearchList',
component: '/list/search-list',
locale: 'menu.list.search-list',
},
{
id: 31,
parentId: 30,
path: '/list/search-list/articles',
title: '搜索列表(文章)',
name: 'SearchListArticles',
component: '/list/search-list/articles',
locale: 'menu.list.search-list.articles',
},
{
id: 32,
parentId: 30,
path: '/list/search-list/projects',
title: '搜索列表(项目)',
name: 'SearchListProjects',
component: '/list/search-list/projects',
locale: 'menu.list.search-list.projects',
},
{
id: 33,
parentId: 30,
path: '/list/search-list/applications',
title: '搜索列表(应用)',
name: 'SearchListApplications',
component: '/list/search-list/applications',
locale: 'menu.list.search-list.applications',
},
{
id: 34,
parentId: 26,
path: '/list/basic-list',
title: '标准列表',
name: 'BasicCard',
component: '/list/basic-list',
locale: 'menu.list.basic-list',
},
{
id: 35,
parentId: 28,
path: '/profile/advanced',
title: '高级详细页',
name: 'ProfileAdvanced',
component: '/profile/advanced/index',
locale: 'menu.profile.advanced',
},
{
id: 4,
parentId: 3,
title: '基础表单',
component: '/form/basic-form/index',
path: '/form/basic-form',
name: 'FormBasic',
keepAlive: false,
locale: 'menu.form.basic-form',
},
{
id: 36,
parentId: null,
title: '个人页',
icon: 'UserOutlined',
component: 'RouteView',
redirect: '/account/center',
path: '/account',
name: 'Account',
locale: 'menu.account',
},
{
id: 37,
parentId: 36,
path: '/account/center',
title: '个人中心',
name: 'AccountCenter',
component: '/account/center',
locale: 'menu.account.center',
},
{
id: 38,
parentId: 36,
path: '/account/settings',
title: '个人设置',
name: 'AccountSettings',
component: '/account/settings',
locale: 'menu.account.settings',
},
{
id: 39,
parentId: 3,
title: '分步表单',
component: '/form/step-form/index',
path: '/form/step-form',
name: 'FormStep',
keepAlive: false,
locale: 'menu.form.step-form',
},
{
id: 40,
parentId: 3,
title: '高级表单',
component: '/form/advanced-form/index',
path: '/form/advanced-form',
name: 'FormAdvanced',
keepAlive: false,
locale: 'menu.form.advanced-form',
},
{
id: 41,
parentId: 26,
path: '/list/table-list',
title: '查询表格',
name: 'ConsultTable',
component: '/list/table-list',
locale: 'menu.list.consult-table',
},
{
id: 42,
parentId: 1,
title: '监控页',
component: '/dashboard/monitor',
path: '/dashboard/monitor',
name: 'DashboardMonitor',
keepAlive: true,
locale: 'menu.dashboard.monitor',
},
{
id: 43,
parentId: 1,
title: '工作台',
component: '/dashboard/workplace',
path: '/dashboard/workplace',
name: 'DashboardWorkplace',
keepAlive: true,
locale: 'menu.dashboard.workplace',
},
{
id: 44,
parentId: 26,
path: '/list/crud-table',
title: '增删改查表格',
name: 'CrudTable',
component: '/list/crud-table',
locale: 'menu.list.crud-table',
},
{
id: 45,
parentId: 9,
path: '/menu/menu4',
redirect: '/menu/menu4/menu1',
title: '菜单2-1',
component: 'RouteView',
locale: 'menu.menu.menu4',
},
{
id: 46,
parentId: 45,
path: '/menu/menu4/menu1',
component: '/menu/menu-2-1/menu1',
title: '菜单2-1-1',
keepAlive: true,
locale: 'menu.menu4.menu1',
},
{
id: 47,
parentId: 45,
path: '/menu/menu4/menu2',
component: '/menu/menu-2-1/menu2',
title: '菜单2-1-2',
keepAlive: true,
locale: 'menu.menu4.menu2',
},
]
export const accessMenuData = [
{
id: 18,
parentId: 15,
path: '/access/admin',
title: '管理员',
name: 'AccessAdmin',
component: '/access/admin',
locale: 'menu.access.admin',
},
]
export default defineEventHandler((event) => {
const token = event.req.headers.get('Authorization')
// eslint-disable-next-line node/prefer-global/buffer
const username = Buffer.from(token as any, 'base64').toString('utf-8')
return {
code: 200,
msg: '获取成功',
data: [...menuData, ...(username === 'admin' ? accessMenuData : [])],
}
})
+8
View File
@@ -0,0 +1,8 @@
import { defineEventHandler } from 'h3'
export default defineEventHandler(() => {
return {
code: 200,
msg: 'delete',
}
})
+8
View File
@@ -0,0 +1,8 @@
import { defineEventHandler } from 'h3'
export default defineEventHandler(() => {
return {
code: 200,
msg: 'post',
}
})
+8
View File
@@ -0,0 +1,8 @@
import { defineEventHandler } from 'h3'
export default defineEventHandler(() => {
return {
code: 200,
msg: 'put',
}
})
+24
View File
@@ -0,0 +1,24 @@
import { defineEventHandler } from 'h3'
export default defineEventHandler((event) => {
const token = event.req.headers.get('Authorization')
// eslint-disable-next-line node/prefer-global/buffer
const username = Buffer.from(token, 'base64').toString('utf-8')
if (!token) {
return {
code: 401,
msg: '登录失效',
}
}
return {
code: 200,
msg: '获取成功',
data: {
id: 1,
username,
nickname: username === 'admin' ? '超级管理员' : '普通用户',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
roles: username === 'admin' ? ['ADMIN'] : ['USER'],
},
}
})
+19
View File
@@ -0,0 +1,19 @@
<script setup lang="ts">
import { useLayoutMenuProvide } from '~/components/page-container/context'
const appStore = useAppStore()
const { theme } = storeToRefs(appStore)
const { antd } = useI18nLocale()
const layoutMenu = useLayoutMenu()
useLayoutMenuProvide(layoutMenu, appStore)
</script>
<template>
<a-config-provider :theme="theme" :locale="antd">
<a-app class="h-full font-chinese antialiased">
<TokenProvider>
<RouterView />
</TokenProvider>
</a-app>
</a-config-provider>
</template>
+76
View File
@@ -0,0 +1,76 @@
export interface IncidentListParams {
dict_id?: string;
item_value?: string;
pageSize?: number;
pageNum?: number;
}
export interface accidentParams {
ticket_id?: string;
vin?: string;
carline?: string;
model?: string;
labels?: string;
accident_level?: string;
bmbs_name?: string;
rdca_reporter?: string;
software_version?: string;
affects_versions?: string;
components?: string;
fix_versions?: string;
case_description?: string;
fo_solution?: string;
assignee_name?: string;
occur_datetime?: string;
subtasks?: string;
linked_work_items?: string;
}
export async function getDictDataApi(params: IncidentListParams) {
return usePost('/accident/item-list', params)
}
export async function getAllDictCodeApi() {
return useGet('/accident/all_dict_code')
}
export async function insertDictTypetApi(params: IncidentListParams) {
return usePost('/accident/insert-dict', params)
}
export async function insertAccidentApi(params: accidentParams) {
return usePost('/accident/insert-accident', params)
}
export async function getAccidentRecordApi(params: any) {
return usePost('/accident/get-accident-record', params)
}
export async function getAccidentByIdApi(params: number) {
return usePost('/accident/get-accident-by-id', params)
}
export async function updataItemByIdApi(params: any) {
return usePost('/accident/update-item', params)
}
export async function bingImageApi(params: any) {
return usePost('/file_uploads/bind-image', params)
}
export function getImagesApi(case_id: number) {
return useGet(`/file_uploads/get-images/${case_id}`)
}
export async function getSecretKey() {
return useGet('/user/get_record')
}
+37
View File
@@ -0,0 +1,37 @@
export interface LoginParams {
username: string
password: string
type?: 'account'
}
export interface LoginMobileParams {
mobile: string
code: string
type: 'mobile'
}
export interface LoginResultModel {
token: string
}
export function loginApi(params: LoginParams | LoginMobileParams) {
return usePost<LoginResultModel, LoginParams | LoginMobileParams>('/user/login', params, {
// 设置为false的时候不会携带token
token: false,
// 开发模式下使用自定义的接口
customDev: true,
// 是否开启全局请求loading
loading: true,
})
}
// export function loginApi(params: any) {
// return usePost('/user/login', params, {
// token: false,
// loading: true,
// })
// }
export function logoutApi() {
return useGet('/logout')
}
+5
View File
@@ -0,0 +1,5 @@
import type { MenuData } from '~@/layouts/basic-layout/typing'
export function getRouteMenusApi() {
return useGet<MenuData>('/menu')
}
+11
View File
@@ -0,0 +1,11 @@
export interface UserInfo {
id: number | string
username: string
nickname: string
avatar: string
roles?: (string | number)[]
}
export function getUserInfoApi() {
return useGet<UserInfo>('/user/info')
}
+26
View File
@@ -0,0 +1,26 @@
export interface ListResultModel {
id: number
title: string
username: string
password: string
}
export type ListResultParams = Partial<Omit<ListResultModel, 'id' | 'password'>>
export async function getListApi(params?: ListResultParams) {
return usePost<ListResultModel[]>('/list', params)
}
export type CreateListParams = Partial<Omit<ListResultModel, 'id'>>
export async function createListApi(params: CreateListParams) {
return usePost('/list/create', params)
}
export async function editListApi(params: ListResultModel) {
return usePut('/list', params)
}
export async function delListApi(id: string | number) {
return useDelete(`/list/${id}`)
}
+12
View File
@@ -0,0 +1,12 @@
export interface ListResultModel {
id: number
title: string
username: string
password: string
}
export type ListResultParams = Partial<Omit<ListResultModel, 'id' | 'password'>>
export async function getListApi(params?: ListResultParams) {
return usePost<ListResultModel[]>('/list/basic-list', params)
}
+30
View File
@@ -0,0 +1,30 @@
interface CrudTableModel {
id?: number
/**
* 名称
*/
name: string
/**
* 值
*/
value: string
/**
* 描述
*/
remark?: string
}
type CrudTableParams = Partial<Omit<CrudTableModel, 'id'>>
export async function getListApi(params?: CrudTableParams) {
return usePost<CrudTableModel[]>('/list/crud-table', params)
}
export async function deleteApi(id: string | number) {
return useDelete(`/list/${id}`)
}
export type {
CrudTableModel,
CrudTableParams,
}
+46
View File
@@ -0,0 +1,46 @@
import type { STATUS } from '~@/utils/constant'
interface ConsultTableModel {
id: number
/**
* 服务名称
*/
name: string
/**
* 服务调用次数
*/
callNo: 805
/**
* 描述
*/
desc: string
/**
* 状态
*/
status: STATUS
/**
* 上次调用时间
*/
updatedAt: string
// 分页
current?: number
// size
pageSize?: number
}
type ConsultTableParams = Partial<Omit<ConsultTableModel, 'id'>>
export async function getListApi(params?: ConsultTableParams) {
return usePost<ConsultTableModel[]>('/list/consult-list', params)
}
export async function deleteApi(id: string | number) {
return useDelete(`/list/${id}`)
}
export type {
ConsultTableModel,
ConsultTableParams,
STATUS,
}
+3
View File
@@ -0,0 +1,3 @@
export function oauthLoginApi() {
return useGet('/login')
}
+23
View File
@@ -0,0 +1,23 @@
export interface IncidentListParams {
startTime?: string;
endTime?: string;
pageSize?: number;
pageNum?: number;
oneid?: string;
incident_time?: object;
}
export async function getIncidentListApi(params: IncidentListParams) {
return usePost('/incident/list', params, { timeout: 600000 })
}
export async function getGen6IncidentListApi(params: IncidentListParams,code:string) {
return usePost('/gen6/list', params, { timeout: 600000 ,headers:{'X-Encrypted-Timestamp':code}}) //,headers:{'X-Encrypted-Timestamp':code}
}
export async function getGen5IncidentListApi(params: IncidentListParams,code:string) {
return usePost('/gen5/list', params, { timeout: 600000, headers:{'X-Encrypted-Timestamp':code} })
}
+23
View File
@@ -0,0 +1,23 @@
export function test200() {
return useGet('/')
}
export function test401() {
return useGet('/401')
}
export function test500() {
return useGet('/500')
}
export function testPut() {
return usePut('/test')
}
export function testPost() {
return usePost('/test')
}
export function testDelete() {
return useDelete('/test')
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

+61
View File
@@ -0,0 +1,61 @@
.slide-fadein-up-enter-active,
.slide-fadein-up-leave-active {
transition:
opacity 0.3s,
transform 0.4s;
}
.slide-fadein-up-enter-from {
transform: translateY(20px);
opacity: 0;
}
.slide-fadein-up-leave-to {
transform: translateY(-20px);
opacity: 0;
}
.slide-fadein-right-enter-active,
.slide-fadein-right-leave-active {
transition:
opacity 0.3s,
transform 0.3s,
-webkit-transform 0.3s;
}
.slide-fadein-right-enter-from {
transform: translateX(-20px);
opacity: 0;
}
.slide-fadein-right-leave-to {
transform: translateX(20px);
opacity: 0;
}
.zoom-fadein-enter-active,
.zoom-fadein-leave-active {
transition:
transform 0.3s,
opacity 0.3s ease-in-out;
}
.zoom-fadein-enter-from {
transform: scale(0.99);
opacity: 0;
}
.zoom-fadein-leave-to {
transform: scale(1.01);
opacity: 0;
}
.fadein-enter-active,
.fadein-leave-active {
transition: opacity 0.3s ease-in-out !important;
}
.fadein-enter-from,
.fadein-leave-to {
opacity: 0 !important;
}
+42
View File
@@ -0,0 +1,42 @@
@import './motion.css';
html {
--text-color: rgba(0, 0, 0, 0.85);
--text-color-1: rgba(0, 0, 0, 0.45);
--text-color-2: rgba(0, 0, 0, 0.2);
--bg-color: #fff;
--hover-color: rgba(0, 0, 0, 0.025);
--bg-color-container: #f0f2f5;
--c-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
}
html.dark {
--text-color: rgba(229, 224, 216, 0.85);
--text-color-1: rgba(229, 224, 216, 0.45);
--text-color-2: rgba(229, 224, 216, 0.45);
--bg-color: rgb(36, 37, 37);
--hover-color: rgb(42, 44, 55);
--bg-color-container: rgb(42, 44, 44);
--c-shadow: rgba(13, 13, 13, 0.65) 0 2px 8px 0;
}
body {
color: var(--text-color);
background-color: var(--bg-color);
text-rendering: optimizeLegibility;
overflow: hidden;
}
#app,
body,
html {
height: 100%;
}
#app {
overflow-x: hidden;
}
*,
:after,
:before {
box-sizing: border-box;
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

+12
View File
@@ -0,0 +1,12 @@
<script setup lang="ts">
import type { AccessEnum } from '~/utils/constant'
defineProps<{
access: string | number | (string | number)[] | AccessEnum
}>()
const { hasAccess } = useAccess()
</script>
<template>
<slot v-if="hasAccess(access)" />
</template>
@@ -0,0 +1,96 @@
<script lang="tsx">
import type { Component, PropType } from 'vue'
import type { LoadingEnum } from '~#/loading-enum'
import { loadingMap } from '~#/loading-enum'
export default defineComponent({
name: 'BaseLoading',
props: {
text: {
type: String,
default: '正在加载中...',
},
textColor: {
type: String,
default: '#79bbff',
},
background: {
type: String,
default: 'rgba(0, 0, 0, .5)',
},
modal: {
type: Boolean,
default: true,
},
spin: {
type: String as PropType<LoadingEnum>,
default: 'chase',
},
full: {
type: Boolean,
default: false,
},
},
setup(props) {
const renderBasicLoading = () => {
const { spin, textColor } = props
return h(loadingMap.get(spin) as Component, { color: textColor } as any)
}
return () => {
const { background, modal, full, text, textColor } = props
return (
<div
style={modal ? { background } : {}}
class={{ 'loading-container': true, 'is-fullscreen': full }}
>
<div class="loading-wrapper">
{renderBasicLoading() }
<div class="text" style={{ color: textColor }}>
{text}
</div>
</div>
</div>
)
}
},
})
</script>
<style lang="less" scoped>
.loading-container {
position: absolute;
inset: 0;
z-index: 9999;
&.is-fullscreen {
position: fixed;
}
.loading-wrapper {
position: absolute;
top: 50%;
width: 100%;
margin-top: -21px;
text-align: center;
.text {
width: 100%;
margin: 8px 0;
}
.loading-icon {
animation: rotating 1.5s linear infinite;
}
}
}
@keyframes rotating {
0% {
transform: rotate(0);
}
100% {
transform: rotate(1turn);
}
}
</style>
@@ -0,0 +1,101 @@
<script lang="ts" setup>
defineProps({
color: {
type: String,
default: '#3ff9dc',
},
})
</script>
<template>
<div class="chase-wrapper">
<div class="chase-item" />
<div class="chase-item" />
<div class="chase-item" />
<div class="chase-item" />
<div class="chase-item" />
<div class="chase-item" />
</div>
</template>
<style lang="less" scoped>
.chase-wrapper {
display: inline-block;
width: 60px;
height: 60px;
animation: chase-loader 2.5s infinite linear both;
}
.chase-item {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
animation: chase-dot 2s infinite ease-in-out both;
}
.chase-item::before {
display: block;
width: 25%;
height: 25%;
content: '';
background-color: v-bind(color);
border-radius: 100%;
animation: chase-dot-before 2s infinite ease-in-out both;
}
.chase-item:nth-child(1),
.chase-item:nth-child(1)::before {
animation-delay: -1.1s;
}
.chase-item:nth-child(2),
.chase-item:nth-child(2)::before {
animation-delay: -1s;
}
.chase-item:nth-child(3),
.chase-item:nth-child(3)::before {
animation-delay: -0.9s;
}
.chase-item:nth-child(4),
.chase-item:nth-child(4)::before {
animation-delay: -0.8s;
}
.chase-item:nth-child(5),
.chase-item:nth-child(5)::before {
animation-delay: -0.7s;
}
.chase-item:nth-child(6),
.chase-item:nth-child(6)::before {
animation-delay: -0.6s;
}
@keyframes chase-loader {
100% {
transform: rotate(1turn);
}
}
@keyframes chase-dot {
80%,
100% {
transform: rotate(360deg);
}
}
@keyframes chase-dot-before {
50% {
transform: scale(0.4);
}
100%,
0% {
transform: scale(1);
}
}
</style>
@@ -0,0 +1,83 @@
<script lang="ts" setup>
defineProps({
color: {
type: String,
default: '#3ff9dc',
},
})
</script>
<template>
<div class="cube-wrapper">
<div class="cube cube1" />
<div class="cube cube2" />
<div class="cube cube3" />
<div class="cube cube4" />
<div class="cube cube5" />
<div class="cube cube6" />
<div class="cube cube7" />
<div class="cube cube8" />
<div class="cube cube9" />
</div>
</template>
<style lang="less" scoped>
.cube-wrapper {
display: inline-block;
width: 60px;
height: 60px;
}
.cube {
float: left;
width: 33%;
height: 33%;
background-color: v-bind(color);
animation: cube-loader 1.3s infinite ease-in-out;
animation-delay: 0.2s;
}
.cube1 {
animation-delay: 0.2s;
}
.cube2 {
animation-delay: 0.3s;
}
.cube3 {
animation-delay: 0.4s;
}
.cube4 {
animation-delay: 0.1s;
}
.cube5 {
animation-delay: 0.2s;
}
.cube6 {
animation-delay: 0.3s;
}
.cube7 {
animation-delay: 0s;
}
.cube8 {
animation-delay: 0.1s;
}
@keyframes cube-loader {
0%,
70%,
100% {
transform: scale3d(1, 1, 1);
}
35% {
transform: scale3d(0, 0, 1);
}
}
</style>
@@ -0,0 +1,132 @@
<script lang="ts" setup></script>
<template>
<div class="dots-loader" />
</template>
<style lang="less" scoped>
.dots-loader:not(:required) {
position: relative;
display: inline-block;
width: 7px;
height: 7px;
margin-bottom: 30px;
overflow: hidden;
text-indent: -9999px;
background: transparent;
border-radius: 100%;
box-shadow:
#f86 -14px -14px 0 7px,
#fc6 14px -14px 0 7px,
#6d7 14px 14px 0 7px,
#4ae -14px 14px 0 7px;
transform-origin: 50% 50%;
animation: dots-loader 5s infinite ease-in-out;
}
@keyframes dots-loader {
0% {
box-shadow:
#f86 -14px -14px 0 7px,
#fc6 14px -14px 0 7px,
#6d7 14px 14px 0 7px,
#4ae -14px 14px 0 7px;
}
8.33% {
box-shadow:
#f86 14px -14px 0 7px,
#fc6 14px -14px 0 7px,
#6d7 14px 14px 0 7px,
#4ae -14px 14px 0 7px;
}
16.67% {
box-shadow:
#f86 14px 14px 0 7px,
#fc6 14px 14px 0 7px,
#6d7 14px 14px 0 7px,
#4ae -14px 14px 0 7px;
}
25% {
box-shadow:
#f86 -14px 14px 0 7px,
#fc6 -14px 14px 0 7px,
#6d7 -14px 14px 0 7px,
#4ae -14px 14px 0 7px;
}
33.33% {
box-shadow:
#f86 -14px -14px 0 7px,
#fc6 -14px 14px 0 7px,
#6d7 -14px -14px 0 7px,
#4ae -14px -14px 0 7px;
}
41.67% {
box-shadow:
#f86 14px -14px 0 7px,
#fc6 -14px 14px 0 7px,
#6d7 -14px -14px 0 7px,
#4ae 14px -14px 0 7px;
}
50% {
box-shadow:
#f86 14px 14px 0 7px,
#fc6 -14px 14px 0 7px,
#6d7 -14px -14px 0 7px,
#4ae 14px -14px 0 7px;
}
58.33% {
box-shadow:
#f86 -14px 14px 0 7px,
#fc6 -14px 14px 0 7px,
#6d7 -14px -14px 0 7px,
#4ae 14px -14px 0 7px;
}
66.67% {
box-shadow:
#f86 -14px -14px 0 7px,
#fc6 -14px -14px 0 7px,
#6d7 -14px -14px 0 7px,
#4ae 14px -14px 0 7px;
}
75% {
box-shadow:
#f86 14px -14px 0 7px,
#fc6 14px -14px 0 7px,
#6d7 14px -14px 0 7px,
#4ae 14px -14px 0 7px;
}
83.33% {
box-shadow:
#f86 14px 14px 0 7px,
#fc6 14px -14px 0 7px,
#6d7 14px 14px 0 7px,
#4ae 14px 14px 0 7px;
}
91.67% {
box-shadow:
#f86 -14px 14px 0 7px,
#fc6 14px -14px 0 7px,
#6d7 14px 14px 0 7px,
#4ae -14px 14px 0 7px;
}
100% {
box-shadow:
#f86 -14px -14px 0 7px,
#fc6 14px -14px 0 7px,
#6d7 14px 14px 0 7px,
#4ae -14px 14px 0 7px;
}
}
</style>
@@ -0,0 +1,36 @@
<script lang="ts" setup>
defineProps({
color: {
type: String,
default: '#3ff9dc',
},
})
</script>
<template>
<div class="plane-wrapper" />
</template>
<style lang="less" scoped>
.plane-wrapper {
display: inline-block;
width: 60px;
height: 60px;
background-color: v-bind(color);
animation: plane-loader 1.2s infinite ease-in-out;
}
@keyframes plane-loader {
0% {
transform: perspective(120px);
}
50% {
transform: perspective(120px) rotateY(180deg);
}
100% {
transform: perspective(120px) rotateY(180deg) rotateX(180deg);
}
}
</style>
@@ -0,0 +1,68 @@
<script lang="ts" setup></script>
<template>
<div class="preloader-wrapper" />
</template>
<style lang="less" scoped>
.preloader-wrapper {
position: relative;
display: inline-block;
width: 20px;
height: 20px;
}
.preloader-wrapper::before {
position: absolute;
width: 20px;
height: 20px;
content: '';
background: blue;
background: #9b59b6;
border-radius: 20px;
animation: preloader-before 2s infinite ease-in-out;
}
.preloader-wrapper::after {
position: absolute;
left: 22px;
width: 20px;
height: 20px;
content: '';
background: blue;
background: #2ecc71;
border-radius: 20px;
animation: preloader-after 1.5s infinite ease-in-out;
}
@keyframes preloader-before {
0% {
transform: translateX(0) rotate(0deg);
}
50% {
background: #2ecc71;
border-radius: 0;
transform: translateX(50px) scale(1.2) rotate(260deg);
}
100% {
transform: translateX(0) rotate(0deg);
}
}
@keyframes preloader-after {
0% {
transform: translateX(0);
}
50% {
background: #9b59b6;
border-radius: 0;
transform: translateX(-50px) scale(1.2) rotate(-260deg);
}
100% {
transform: translateX(0);
}
}
</style>
@@ -0,0 +1,49 @@
<script lang="ts" setup>
defineProps({
color: {
type: String,
default: '#3ff9dc',
},
})
</script>
<template>
<div class="pulse-wrapper">
<div class="pulse-item one" />
<div class="pulse-item two" />
<div class="pulse-item three" />
</div>
</template>
<style lang="less" scoped>
.pulse-item {
position: relative;
display: inline-block;
width: 20px;
height: 20px;
background-color: v-bind(color);
border-radius: 50%;
animation: pulse-loader 0.4s ease 0s infinite alternate;
}
.two {
margin: 0 15px;
animation: pulse-loader 0.4s ease 0.2s infinite alternate;
}
.three {
animation: pulse-loader 0.4s ease 0.4s infinite alternate;
}
@keyframes pulse-loader {
0% {
opacity: 1;
transform: scale(1);
}
100% {
opacity: 0.5;
transform: scale(0.75);
}
}
</style>
@@ -0,0 +1,57 @@
<script lang="ts" setup>
defineProps({
color: {
type: String,
default: '#3ff9dc',
},
})
</script>
<template>
<div class="rect-wrapper">
<div class="rect-item rect1" />
<div class="rect-item rect2" />
<div class="rect-item rect3" />
<div class="rect-item rect4" />
<div class="rect-item rect5" />
</div>
</template>
<style lang="less" scoped>
.rect-item {
display: inline-block;
width: 6px;
height: 60px;
margin: 0 4px;
background-color: v-bind(color);
animation: rect-loader 1.2s infinite ease-in-out;
}
.rect2 {
animation-delay: -1.1s;
}
.rect3 {
animation-delay: -1s;
}
.rect4 {
animation-delay: -0.9s;
}
.rect5 {
animation-delay: -0.8s;
}
@keyframes rect-loader {
0%,
40%,
100% {
transform: scaleY(0.4);
}
20% {
transform: scaleY(1);
}
}
</style>
@@ -0,0 +1,13 @@
<script setup lang="ts">
// import { ReadOutlined } from '@ant-design/icons-vue'
</script>
<template>
<!-- <a href="https://docs.antdv-pro.com/" target="_blank" hover="bg-[var(--hover-color)]" flex items-center h-48px px-12px text-16px class="transition-all-300">
<ReadOutlined />
</a> -->
</template>
<style scoped lang="less">
</style>
+15
View File
@@ -0,0 +1,15 @@
<script setup lang="ts">
import { GithubOutlined } from '@ant-design/icons-vue'
defineOptions({
name: 'FooterLinks',
})
</script>
<template>
<a decoration-none href="https://antdv-pro.com" target="_blank">Antdv Pro</a>
<a decoration-none href="https://github.com/antdv-pro/antdv-admin-pro" target="_blank">
<GithubOutlined />
</a>
<a decoration-none href="https://antdv.com" target="_blank">Ant Design Vue</a>
</template>
@@ -0,0 +1,21 @@
@footer-toolbar-prefix-cls: ~'ant-pro-footer-toolbar';
.@{footer-toolbar-prefix-cls} {
position: fixed;
right: 0;
bottom: 0;
z-index: 9;
width: 100%;
height: 56px;
padding: 0 24px;
line-height: 56px;
background: var(--bg-color);
box-shadow: var(--c-shadow);
//border-top: 1px solid #e8e8e8;
&::after {
display: block;
clear: both;
content: '';
}
}
@@ -0,0 +1,15 @@
# FooterToolbar 底部工具栏
固定在底部的工具栏。
## 何时使用
固定在内容区域的底部,不随滚动条移动,常用于长页面的数据搜集和提交工作。
## 代码演示
```html
<footer-tool-bar right="扩展信息提示">
<a-button type="primary" @click="validate" :loading="loading">提交</a-button>
</footer-tool-bar>
```
@@ -0,0 +1,44 @@
<script setup lang="ts">
import { useLayoutState } from '~/layouts/basic-layout/context.ts'
defineOptions({
name: 'FooterToolBar',
})
const prefixCls = shallowRef('ant-pro-footer-toolbar')
const { siderWidth: layoutSiderWidth, collapsed, collapsedWidth, isMobile, layout } = useLayoutState()
const barWidth = computed(() => {
if (isMobile.value || layout.value === 'top')
return undefined
return `calc(100% - ${collapsed.value ? collapsedWidth.value : layoutSiderWidth.value}px)`
})
</script>
<template>
<div :class="prefixCls" :style="{ width: barWidth, transition: '0.3s all' }">
<div class="footer-tool-bar__left">
<slot name="left" />
</div>
<div class="footer-tool-bar__center">
<slot />
</div>
<div class="footer-tool-bar__right">
<slot name="right" />
</div>
</div>
<div class="h-56px" />
</template>
<style scoped lang="less">
@import './index.less';
.footer-tool-bar__left {
float: left;
}
.footer-tool-bar__right {
float: right;
}
.footer-tool-bar__center {
float: none;
}
</style>
@@ -0,0 +1,12 @@
<script setup lang="ts">
</script>
<template>
<!-- <a href="https://gitee.com/antdv-pro/antdv-pro" target="_blank" hover="bg-[var(--hover-color)]" flex items-center h-48px px-12px text-16px class="transition-all-300">
<svg t="1692675850475" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4159" width="16" height="16"><path d="M512 1024C229.222 1024 0 794.778 0 512S229.222 0 512 0s512 229.222 512 512-229.222 512-512 512z m259.149-568.883h-290.74a25.293 25.293 0 0 0-25.292 25.293l-0.026 63.206c0 13.952 11.315 25.293 25.267 25.293h177.024c13.978 0 25.293 11.315 25.293 25.267v12.646a75.853 75.853 0 0 1-75.853 75.853h-240.23a25.293 25.293 0 0 1-25.267-25.293V417.203a75.853 75.853 0 0 1 75.827-75.853h353.946a25.293 25.293 0 0 0 25.267-25.292l0.077-63.207a25.293 25.293 0 0 0-25.268-25.293H417.152a189.62 189.62 0 0 0-189.62 189.645V771.15c0 13.977 11.316 25.293 25.294 25.293h372.94a170.65 170.65 0 0 0 170.65-170.65V480.384a25.293 25.293 0 0 0-25.293-25.267z" fill="#C71D23" p-id="4160" /></svg>
</a> -->
</template>
<style scoped lang="less">
</style>
@@ -0,0 +1,13 @@
<script setup lang="ts">
// import { GithubOutlined } from '@ant-design/icons-vue'
</script>
<template>
<!-- <a href="https://github.com/antdv-pro/antdv-pro" target="_blank" hover="bg-[var(--hover-color)]" flex items-center h-48px px-12px text-16px class="transition-all-300">
<GithubOutlined />
</a> -->
</template>
<style scoped lang="less">
</style>
@@ -0,0 +1,9 @@
<script lang="ts">
export default {
name: 'CarbonTranslate',
}
</script>
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 32 32"><path fill="currentColor" d="M27.85 29H30l-6-15h-2.35l-6 15h2.15l1.6-4h6.85zm-7.65-6l2.62-6.56L25.45 23zM18 7V5h-7V2H9v3H2v2h10.74a14.71 14.71 0 0 1-3.19 6.18A13.5 13.5 0 0 1 7.26 9h-2.1a16.47 16.47 0 0 0 3 5.58A16.84 16.84 0 0 1 3 18l.75 1.86A18.47 18.47 0 0 0 9.53 16a16.92 16.92 0 0 0 5.76 3.84L16 18a14.48 14.48 0 0 1-5.12-3.37A17.64 17.64 0 0 0 14.8 7z" /></svg>
</template>
@@ -0,0 +1,9 @@
<script lang="ts">
export default {
name: 'CarbonMoon',
}
</script>
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 32 32"><path fill="currentColor" d="M13.502 5.414a15.075 15.075 0 0 0 11.594 18.194a11.113 11.113 0 0 1-7.975 3.39c-.138 0-.278.005-.418 0a11.094 11.094 0 0 1-3.2-21.584M14.98 3a1.002 1.002 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.072 13.072 0 0 0 10.703-5.555a1.01 1.01 0 0 0-.783-1.565A13.08 13.08 0 0 1 15.89 4.38A1.015 1.015 0 0 0 14.98 3Z" /></svg>
</template>
@@ -0,0 +1,9 @@
<script lang="ts">
export default {
name: 'CarbonSun',
}
</script>
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 32 32"><path fill="currentColor" d="M16 12.005a4 4 0 1 1-4 4a4.005 4.005 0 0 1 4-4m0-2a6 6 0 1 0 6 6a6 6 0 0 0-6-6ZM5.394 6.813L6.81 5.399l3.505 3.506L8.9 10.319zM2 15.005h5v2H2zm3.394 10.193L8.9 21.692l1.414 1.414l-3.505 3.506zM15 25.005h2v5h-2zm6.687-1.9l1.414-1.414l3.506 3.506l-1.414 1.414zm3.313-8.1h5v2h-5zm-3.313-6.101l3.506-3.506l1.414 1.414l-3.506 3.506zM15 2.005h2v5h-2z" /></svg>
</template>
@@ -0,0 +1,10 @@
import type { useAppStore } from '~/stores/app.ts'
export const LayoutMenuKey = Symbol('LayoutMenu')
export function useLayoutMenuProvide(layoutMenu: ReturnType<typeof useLayoutMenu>, appStore: ReturnType<typeof useAppStore>) {
provide(LayoutMenuKey, { layoutMenu, appStore })
}
export function useLayoutMenuInject() {
return inject(LayoutMenuKey, {} as any)
}
@@ -0,0 +1,125 @@
<script setup lang="ts">
import type { VNodeChild } from 'vue'
import { isFunction } from '@v-c/utils'
import { useLayoutState } from '~/layouts/basic-layout/context'
import { useLayoutMenuInject } from './context.ts'
defineProps<{
title?: string
}>()
const slots = defineSlots<{
default: (props: any) => any
title?: (props: any) => any
content?: (props: any) => any
extraContent?: (props: any) => any
extra?: (props: any) => any
footer?: (props: any) => any
}>()
const { layoutMenu: layoutMenuStore, appStore } = useLayoutMenuInject()
const { layoutSetting } = (storeToRefs as any)(appStore)
const { menuDataMap } = (storeToRefs as any)(layoutMenuStore)
const route = useRoute()
function getCurrentItem() {
const key: string = route.meta?.originPath ?? route.path
if (key && menuDataMap.value.has(key))
return menuDataMap.value.get(key)
return {} as any
}
const currentItem = shallowRef(getCurrentItem())
onBeforeMount(() => {
currentItem.value = getCurrentItem()
})
let timer: ReturnType<typeof setTimeout> | undefined
watch(() => route.path, () => {
if (timer) {
clearTimeout(timer)
timer = undefined
}
timer = setTimeout(() => {
currentItem.value = getCurrentItem()
}, 300)
})
// const { contentWidth } = useLayoutState()
const layoutState = useLayoutState()
// ✅ 修复:使用 computed 属性安全访问,提供默认值
const contentWidth = computed(() => {
return layoutState?.contentWidth || 'Fluid' // 提供安全默认值
})
const contentCls = computed(() => {
const cls: string[] = [
'flex flex-col flex-1',
]
// ✅ 修复:使用 contentWidth.value
if (contentWidth.value === 'Fluid')
cls.push('w-full')
else if (contentWidth.value === 'Fixed')
cls.push(...['max-w-1200px w-1200px', 'mx-auto'])
return cls
})
// const contentCls = computed(() => {
// const cls: string[] = [
// 'flex flex-col flex-1',
// ]
// if (contentWidth.value === 'Fluid')
// cls.push('w-full')
// else if (contentWidth.value === 'Fixed')
// cls.push(...['max-w-1200px w-1200px', 'mx-auto'])
// return cls
// })
function renderTitle(title: VNodeChild | (() => VNodeChild)) {
if (isFunction(title))
return title()
return title
}
</script>
<template>
<div class="ant-pro-page-container">
<div class="bg-[var(--bg-color)]" :class="layoutSetting.multiTab ? 'pb-16px' : 'py-16px'" px-24px mb-24px mx--24px mt--24px>
<a-breadcrumb v-if="!currentItem.hideInBreadcrumb">
<template v-if="currentItem.matched?.length">
<a-breadcrumb-item v-for="item in currentItem.matched" :key="item.path">
{{ renderTitle(item.title) }}
</a-breadcrumb-item>
</template>
<a-breadcrumb-item>
{{ renderTitle(currentItem.title) }}
</a-breadcrumb-item>
</a-breadcrumb>
<div flex mt-8px justify-between>
<div flex items-center my-4px of-hidden>
<slot name="title">
<span text-20px line-height-32px mr-12px mb-0 truncate font-600>{{ renderTitle(title ?? currentItem.title) }}</span>
</slot>
</div>
<div>
<slot name="extra" />
</div>
</div>
<div v-if="slots.content || slots.extraContent" pt-12px>
<div flex w-full>
<div flex-auto>
<slot name="content" />
</div>
<div flex-shrink-0>
<slot name="extraContent" />
</div>
</div>
</div>
<slot name="footer" />
</div>
<div :class="contentCls">
<slot />
</div>
</div>
</template>
@@ -0,0 +1,34 @@
<script setup lang="ts">
const { locale, setLocale } = useI18nLocale()
function handleClick({ key }: any) {
setLocale(key)
}
</script>
<template>
<a-dropdown>
<span hover="bg-[var(--hover-color)]" flex items-center h-48px px-12px text-16px cursor-pointer class="transition-all-300">
<CarbonLanguage class="anticon" />
</span>
<template #overlay>
<a-menu :selected-keys="[locale]" @click="handleClick">
<a-menu-item key="zh-CN">
<template #icon>
<span>
🇨🇳
</span>
</template>
简体中文
</a-menu-item>
<a-menu-item key="en-US">
<template #icon>
<span>
🇺🇸
</span>
</template>
English
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</template>
@@ -0,0 +1,25 @@
<script setup lang="ts">
import { App, theme } from 'ant-design-vue'
import { registerTokenToCSSVar } from './token-to-cssvar'
defineOptions({
name: 'TokenProvider',
})
const { token } = theme.useToken()
const { setToken } = useAntdToken()
const { message, modal, notification } = App.useApp()
useSetGlobalConfig({
message,
modal,
notification,
} as any)
watchEffect(() => {
setToken(token.value)
registerTokenToCSSVar(token.value)
})
</script>
<template>
<slot />
</template>
@@ -0,0 +1,29 @@
import type { GlobalToken } from 'ant-design-vue/es/theme'
import { canUseDom, updateCSS } from '@v-c/utils'
import { kebabCase } from 'lodash'
function formatKey(key: string, prefixCls: string) {
return `${prefixCls}${kebabCase(key)}`
}
const prefixCls = '--pro-ant-'
const dynamicStyleMark = `${prefixCls}${Date.now()}-${Math.random()}`
export function registerTokenToCSSVar(token: GlobalToken) {
// TODO
const variables: Record<string, any> = {}
if (!token)
return
for (const key in token) {
const val = token[key as keyof GlobalToken]
variables[formatKey(key, prefixCls)] = val
}
const cssList = Object.keys(variables).map(key => `${key}: ${variables[key]};`)
if (canUseDom()) {
updateCSS(
`:root {${cssList.join('\n')}}`,
`${dynamicStyleMark}-dynamic-theme`,
)
}
}
@@ -0,0 +1,98 @@
<script setup lang="ts">
import { LogoutOutlined} from "@ant-design/icons-vue";
const message = useMessage();
const userStore = useUserStore();
const multiTabStore = useMultiTab();
const layoutMenuStore = useLayoutMenu();
const router = useRouter();
const { avatar, nickname } = storeToRefs(userStore);
// async function handleClick({ key }: any) {
// if (key === 'logout') {
// console.log("logout123")
// const hide = message.loading('退出登录...', 0)
// try {
// await userStore.logout()
// }
// finally {
// hide()
// message.success('退出登录成功', 3)
// router.push({
// path: '/',
// }).then(() => {
// multiTabStore.clear()
// layoutMenuStore.clear()
// })
// }
// }
// }
async function handleClick({ key }: any) {
if (key === "logout") {
console.log("logout123");
const hide = message.loading("退出登录...", 0);
try {
const res = await userStore.logout();
// console.log("Logout response:", res);
// 登出成功后执行跳转
message.success("Logged out successfully", 3);
router
.push({
path: "/login",
})
.then(() => {
multiTabStore.clear();
layoutMenuStore.clear();
});
} catch (error) {
console.error("登出失败:", error);
message.error("退出登录失败,请重试", 3);
} finally {
hide(); // 确保 loading 总是关闭
}
}
}
</script>
<template>
<a-dropdown>
<span
hover="bg-[var(--hover-color)]"
flex
items-center
h-48px
px-12px
cursor-pointer
class="transition-all-300"
>
<a-avatar :src="avatar" mr-8px size="small" />
<span class="anticon">{{ nickname }}</span>
</span>
<template #overlay>
<a-menu @click="handleClick">
<!-- <a-menu-item key="0">
<template #icon>
<UserOutlined />
</template>
<RouterLink to="/account/center">
个人中心
</RouterLink>
</a-menu-item>
<a-menu-item key="1">
<template #icon>
<ProfileOutlined />
</template>
<RouterLink to="/account/settings">
个人设置
</RouterLink>
</a-menu-item> -->
<a-menu-divider />
<a-menu-item key="logout">
<template #icon>
<LogoutOutlined />
</template>
logout
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</template>
@@ -0,0 +1,167 @@
<script setup lang="ts">
interface Prop {
// 列表每一项的高度
dataSource: any[]
itemHeight?: number
}
const props = withDefaults(defineProps<Prop>(), {
itemHeight: 80,
})
const transformY = ref<number>()
const transformStyle = computed<string>(() => {
return `transform: translateY(${transformY.value}px)`
})
const scrollerContainerRef = ref<HTMLDivElement>()
// 容器高度
const scrollerContainerRefHeight = computed(() => {
return scrollerContainerRef.value ? scrollerContainerRef.value.offsetHeight : 0
})
// 渲染视口的item数量
const itemCount = computed<number>(() => {
return Math.ceil(scrollerContainerRefHeight.value / props.itemHeight) + 1
})
// 最顶端和低端元素在数组中的索引
const start = ref<number>(0)
const end = computed<number>(() => {
return start.value + itemCount.value
})
const Data = ref<any[]>()
// 用来撑开容器高度
const pillarHeight = computed(() => {
if (Data.value?.length)
return props.itemHeight * Data.value?.length
return undefined
})
const renderData = computed(() => {
const _start = Math.max(0, start.value)
const _end = Math.min(end.value, Data.value!.length)
return Data.value?.slice(_start, _end)
})
function init() {
if (!props.dataSource) {
const res = Array.from({ length: 10000 })
res.forEach((_, i) => {
res[i] = i
})
Data.value = res
}
else {
Data.value = props.dataSource
}
}
function handleScroll(e: Event) {
const scrollTop = (e.target as HTMLDivElement).scrollTop
start.value = Math.floor(scrollTop / props.itemHeight)
transformY.value = start.value * props.itemHeight
}
onMounted(() => {
init()
})
</script>
<template>
<div v-if="Data" class="list-container">
<div ref="scrollerContainerRef" class="scroller-container scrollbar" @scroll="handleScroll">
<div class="pillar" :style="{ height: `${pillarHeight}px` }" />
<div class="list" :style="transformStyle">
<div v-for="(item, index) in renderData" :key="index" class="item" :style="{ height: `${itemHeight}px` }">
<slot name="renderItem" :item="item" />
</div>
</div>
</div>
</div>
</template>
<style scoped lang="less">
.scrollbar {
&::-webkit-scrollbar {
width: 5px;
height: 10px;
}
&::-webkit-scrollbar-thumb {
border-radius: 5px;
-webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
background: rgba(190, 190, 190, 0.2);
}
&::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 5px rgba(227, 227, 227, 0.2);
border-radius: 0;
background: rgba(0, 0, 0, 0.1);
}
}
.list-container {
height: 350px;
width: 100%;
background-color: var(--bg-color);
.scroller-container {
position: relative;
width: 100%;
height: 100%;
overflow: auto;
// 处理ios滚动卡顿
--webkit-overflow-scrolling: touch;
.pillar {
position: absolute;
left: 0;
top: 0;
right: 0;
z-index: -1;
}
.list {
position: absolute;
top: 0;
left: 0;
right: 0;
.item {
box-sizing: border-box;
display: flex;
align-items: center;
width: 100%;
height: 50px;
padding: 0 20px;
border-bottom: 1px solid var(--bg-color-container);
:deep(.ant-list-item) {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
.ant-list-item-action {
margin-top: 18px;
}
& > div:nth-child(1) {
flex: 1;
}
}
:deep(.ant-list-item-meta) {
display: flex;
.ant-list-item-meta-avatar {
margin-right: 15px;
}
}
}
}
}
}
</style>
+16
View File
@@ -0,0 +1,16 @@
import type { AccessEnum } from '~@/utils/constant'
import { toArray } from '@v-c/utils'
export function useAccess() {
const userStore = useUserStore()
const roles = computed(() => userStore.roles)
const hasAccess = (roles: (string | number)[] | string | number | AccessEnum) => {
const accessRoles = userStore.roles
const roleArr = toArray(roles).flat(1)
return roleArr.some(role => accessRoles?.includes(role))
}
return {
hasAccess,
roles,
}
}
+14
View File
@@ -0,0 +1,14 @@
import type { GlobalToken } from 'ant-design-vue/es/theme'
import { theme } from 'ant-design-vue'
export const useAntdToken = createSharedComposable(() => {
const { token: antdToken } = theme.useToken()
const token = ref<GlobalToken>(antdToken.value)
const setToken = (globalToken: GlobalToken) => {
token.value = globalToken
}
return {
token,
setToken,
}
})
+8
View File
@@ -0,0 +1,8 @@
import { useDelete, useGet, usePost, usePut } from '~/utils/request'
export {
useDelete,
useGet,
usePost,
usePut,
}
@@ -0,0 +1,3 @@
export const STORAGE_AUTHORIZE_KEY = 'Authorization'
export const useAuthorization = createGlobalState(() => useStorage<null | string>(STORAGE_AUTHORIZE_KEY, null))
+51
View File
@@ -0,0 +1,51 @@
import type { LoadingEnum } from '~#/loading-enum'
import baseLoading from '@/components/base-loading/index.vue'
interface LoadingType {
text?: string
textColor?: string
background?: string
spin?: LoadingEnum
minTime?: number
modal?: boolean
}
/**
* 全局loading配置
* @param config
*/
export function useLoading(config: LoadingType = {}) {
const loadingConstructor = createApp(baseLoading, { ...config })
let instance: any = null
let startTime = 0
let endTime = 0
const minTime = config.minTime || 0
const open = (target: HTMLElement = document.body) => {
if (!instance)
instance = loadingConstructor.mount(document.createElement('div'))
if (!instance || !instance.$el)
return
target?.appendChild?.(instance.$el)
startTime = performance.now()
}
const close = () => {
if (!instance || !instance.$el)
return
endTime = performance.now()
if (endTime - startTime < minTime) {
setTimeout(() => {
instance.$el.parentNode?.removeChild(instance.$el)
}, Math.floor(minTime - (endTime - startTime)))
}
else {
instance.$el.parentNode?.removeChild(instance.$el)
}
}
return {
open,
close,
}
}
+40
View File
@@ -0,0 +1,40 @@
import type { VNode } from 'vue'
import { createVNode } from 'vue'
const compMap = new Map<string, VNode>()
export function useCompConsumer() {
const route = useRoute()
const getComp = (component: VNode): VNode => {
// 判断当前是否包含name,如果不包含name,那就直接处理掉name
if (!route.name)
return component
// 获取当前组件的name
// @ts-expect-error this is obj
const compName = component?.type?.name
const routeName = route.name as string
if (compMap.has(routeName))
return compMap.get(routeName) as VNode
// 不存在的情况下,就需要进行组织
const node = component
if (compName && compName === routeName) {
compMap.set(routeName, node)
return node
}
const Comp = createVNode(node)
if (!Comp.type)
Comp.type = {}
// @ts-expect-error this is obj
Comp.type.name = routeName
compMap.set(routeName, Comp)
return Comp
}
return {
getComp,
}
}
+15
View File
@@ -0,0 +1,15 @@
import router from '@/router'
export function useCurrentRoute() {
const currentRoute = router.currentRoute
const layoutMenuStore = useLayoutMenu()
const { menuDataMap } = storeToRefs(layoutMenuStore)
const pathsKeys = menuDataMap.value?.keys()
const currentPath = currentRoute.value.path
// router.
// 通过校验判断是否在menuItem中
console.log('currentPath', currentPath, pathsKeys)
return {
currentRoute,
}
}
+36
View File
@@ -0,0 +1,36 @@
import type { message, notification } from 'ant-design-vue'
import type { ModalFunc } from 'ant-design-vue/es/modal/Modal'
interface GlobalConfigIntl {
message?: Omit<typeof message, 'useMessage'>
modal?: {
readonly info: ModalFunc
readonly success: ModalFunc
readonly error: ModalFunc
readonly warning: ModalFunc
readonly confirm: ModalFunc
}
notification?: Omit<typeof notification, 'useNotification'>
}
const globalConfig = reactive<GlobalConfigIntl>({})
export function useGlobalConfig() {
return globalConfig
}
export function useSetGlobalConfig(config: GlobalConfigIntl) {
globalConfig.message = config.message
globalConfig.modal = config.modal
globalConfig.notification = config.notification
}
export function useMessage() {
return globalConfig.message!
}
export function useModal() {
return globalConfig.modal!
}
export function useNotification() {
return globalConfig.notification!
}

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