first commit

main
魏灿斌 6 days ago
commit 7cdaecc04b

@ -0,0 +1,5 @@
Android 4.1
IOS 7.1
Chrome > 31
ff > 31
ie >= 11

@ -0,0 +1,23 @@
# 环境
VITE_NODE_ENV=development
# 接口前缀
VITE_API_BASE_PATH=
# 打包路径
VITE_BASE_PATH=/
# 标题
VITE_APP_TITLE=ElementAdmin
# 是否全量引入element-plus样式
VITE_USE_ALL_ELEMENT_PLUS_STYLE=true
# 是否开启mock
VITE_USE_MOCK=true
# 是否使用在线图标
VITE_USE_ONLINE_ICON=true
# 是否隐藏全局设置按钮
VITE_HIDE_GLOBAL_SETTING=false

@ -0,0 +1,41 @@
# 环境
VITE_NODE_ENV=production
# 接口前缀
VITE_API_BASE_PATH=
# 打包路径
VITE_BASE_PATH=/dist-dev/
# 是否删除debugger
VITE_DROP_DEBUGGER=false
# 是否删除console.log
VITE_DROP_CONSOLE=false
# 是否sourcemap
VITE_SOURCEMAP=true
# 输出路径
VITE_OUT_DIR=dist-dev
# 标题
VITE_APP_TITLE=ElementAdmin
# 是否包分析
VITE_USE_BUNDLE_ANALYZER=false
# 是否全量引入element-plus样式
VITE_USE_ALL_ELEMENT_PLUS_STYLE=false
# 是否开启mock
VITE_USE_MOCK=true
# 是否切割css
VITE_USE_CSS_SPLIT=true
# 是否使用在线图标
VITE_USE_ONLINE_ICON=true
# 是否隐藏全局设置按钮
VITE_HIDE_GLOBAL_SETTING=false

@ -0,0 +1,41 @@
# 环境
VITE_NODE_ENV=production
# 接口前缀
VITE_API_BASE_PATH=
# 打包路径
VITE_BASE_PATH=/vue-element-plus-admin/
# 是否删除debugger
VITE_DROP_DEBUGGER=true
# 是否删除console.log
VITE_DROP_CONSOLE=true
# 是否sourcemap
VITE_SOURCEMAP=false
# 输出路径
VITE_OUT_DIR=dist-pro
# 标题
VITE_APP_TITLE=ElementAdmin
# 是否包分析
VITE_USE_BUNDLE_ANALYZER=false
# 是否全量引入element-plus样式
VITE_USE_ALL_ELEMENT_PLUS_STYLE=false
# 是否开启mock
VITE_USE_MOCK=true
# 是否切割css
VITE_USE_CSS_SPLIT=true
# 是否使用在线图标
VITE_USE_ONLINE_ICON=true
# 是否隐藏全局设置按钮
VITE_HIDE_GLOBAL_SETTING=false

@ -0,0 +1,41 @@
# 环境
VITE_NODE_ENV=production
# 接口前缀
VITE_API_BASE_PATH=
# 打包路径
VITE_BASE_PATH=/
# 是否删除debugger
VITE_DROP_DEBUGGER=true
# 是否删除console.log
VITE_DROP_CONSOLE=true
# 是否sourcemap
VITE_SOURCEMAP=false
# 输出路径
VITE_OUT_DIR=dist-pro
# 标题
VITE_APP_TITLE=ElementAdmin
# 是否包分析
VITE_USE_BUNDLE_ANALYZER=true
# 是否全量引入element-plus样式
VITE_USE_ALL_ELEMENT_PLUS_STYLE=false
# 是否开启mock
VITE_USE_MOCK=true
# 是否切割css
VITE_USE_CSS_SPLIT=true
# 是否使用在线图标
VITE_USE_ONLINE_ICON=false
# 是否隐藏全局设置按钮
VITE_HIDE_GLOBAL_SETTING=false

@ -0,0 +1,41 @@
# 环境
VITE_NODE_ENV=production
# 接口前缀
VITE_API_BASE_PATH=
# 打包路径
VITE_BASE_PATH=/dist-test/
# 是否删除debugger
VITE_DROP_DEBUGGER=false
# 是否删除console.log
VITE_DROP_CONSOLE=false
# 是否sourcemap
VITE_SOURCEMAP=true
# 输出路径
VITE_OUT_DIR=dist-test
# 标题
VITE_APP_TITLE=ElementAdmin
# 是否包分析
VITE_USE_BUNDLE_ANALYZER=false
# 是否全量引入element-plus样式
VITE_USE_ALL_ELEMENT_PLUS_STYLE=false
# 是否开启mock
VITE_USE_MOCK=true
# 是否切割css
VITE_USE_CSS_SPLIT=false
# 是否使用在线图标
VITE_USE_ONLINE_ICON=true
# 是否隐藏全局设置按钮
VITE_HIDE_GLOBAL_SETTING=false

@ -0,0 +1,131 @@
name: Automerge
on:
pull_request:
types:
- labeled
- unlabeled
- synchronize
- opened
- edited
- ready_for_review
- reopened
- unlocked
pull_request_review:
types:
- submitted
status: {}
jobs:
# 合并发布版本的 pr 到 master
auto-merge:
runs-on: ubuntu-latest
steps:
- name: Automerge
uses: 'pascalgn/automerge-action@v0.14.3'
env:
BASE_BRANCHES: 'release'
GITHUB_TOKEN: '${{ secrets.TOKEN }}'
MERGE_LABELS: ''
MERGE_FILTER_AUTHOR: 'kailong321200875'
push-to-gh-pages:
needs: [auto-merge]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Pnpm
uses: pnpm/action-setup@v2
with:
version: latest
- name: use Node.js 18
uses: actions/setup-node@v2.1.2
with:
node-version: '18.x'
- name: Set SSH Environment
env:
DOCS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }}
run: |
mkdir -p ~/.ssh/
echo "$ACTIONS_DEPLOY_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan github.com > ~/.ssh/known_hosts
chmod 700 ~/.ssh && chmod 600 ~/.ssh/*
git config --local user.email "321200875@qq.com"
git config --local user.name "kailong321200875"
# 发布到 github
- name: Build Github
run: |
pnpm install --no-frozen-lockfile
pnpm run build:pro
- name: Deploy Github
uses: peaceiris/actions-gh-pages@v3
with:
deploy_key: ${{secrets.ACTIONS_DEPLOY_KEY}}
publish_branch: gh-pages
publish_dir: ./dist-pro
force_orphan: true
cname: element-plus-admin.cn
push-to-gh-pages-gitee:
needs: [auto-merge]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Pnpm
uses: pnpm/action-setup@v2
with:
version: latest
- name: use Node.js 18
uses: actions/setup-node@v2.1.2
with:
node-version: '18.x'
- name: Set SSH Environment
env:
DOCS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }}
run: |
mkdir -p ~/.ssh/
echo "$ACTIONS_DEPLOY_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan github.com > ~/.ssh/known_hosts
chmod 700 ~/.ssh && chmod 600 ~/.ssh/*
git config --local user.email "321200875@qq.com"
git config --local user.name "kailong321200875"
- name: Build Gitee
run: |
pnpm install --no-frozen-lockfile
pnpm run build:gitee
# 发布到 gitee
- name: Deploy Gitee
uses: peaceiris/actions-gh-pages@v3
with:
deploy_key: ${{secrets.ACTIONS_DEPLOY_KEY}}
publish_branch: gh-pages-gitee
publish_dir: ./dist-pro
force_orphan: true
- name: Sync Github Repos To Gitee # 名字随便起
uses: Yikun/hub-mirror-action@v1.1 # 使用Yikun/hub-mirror-action
with:
src: github/kailong321200875 # 源端账户名(github)
dst: gitee/kailong110120130 # 目的端账户名(gitee)
dst_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} # SSH密钥对中的私钥
dst_token: ${{ secrets.GITEE_TOKEN }} # Gitee账户的私人令牌
account_type: user # 账户类型
clone_style: 'https' # 使用https方式进行clone也可以使用ssh
debug: true # 启用后会显示所有执行命令
force_update: true # 启用后,强制同步,即强制覆盖目的端仓库
static_list: 'vue-element-plus-admin' # 静态同步列表,在此填写需要同步的仓库名称,可填写多个
timeout: '600s' # git超时设置超时后会自动重试git操作

@ -0,0 +1,19 @@
on:
push:
branches:
- release
name: Release
jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: GoogleCloudPlatform/release-please-action@v3
id: release
with:
token: ${{ secrets.TOKEN }}
default-branch: release
release-type: node
package-name: standard-version
changelog-types: '[{"type": "types", "section":"Types", "hidden": false},{"type": "revert", "section":"Reverts", "hidden": false},{"type": "feat", "section": "Features", "hidden": false},{"type": "fix", "section": "Bug Fixes", "hidden": false},{"type": "improvement", "section": "Feature Improvements", "hidden": false},{"type": "docs", "section":"Docs", "hidden": false},{"type": "style", "section":"Styling", "hidden": false},{"type": "refactor", "section":"Code Refactoring", "hidden": false},{"type": "perf", "section":"Performance Improvements", "hidden": false},{"type": "test", "section":"Tests", "hidden": false},{"type": "build", "section":"Build System", "hidden": false},{"type": "ci", "section":"CI", "hidden":false}]'

9
.gitignore vendored

@ -0,0 +1,9 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
/dist*
pnpm-debug
stats.html
.idea

@ -0,0 +1 @@
npx --no -- commitlint --edit $1

@ -0,0 +1,9 @@
module.exports = {
'*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'],
'{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': ['prettier --parser json --write'],
'package.json': ['prettier --write'],
'*.vue': ['prettier --write', 'stylelint --fix'],
'*.{scss,less,styl,css,html}': ['stylelint --fix', 'prettier --write'],
'*.md': ['prettier --write'],
'*.hbs': ['prettier --write']
}

@ -0,0 +1,2 @@
npm run ts:check
npm run lint:lint-staged

@ -0,0 +1,9 @@
/node_modules/**
/dist/
/dist*
/public/*
/docs/*
/src/types/env.d.ts
/docs/**/*
/plop/**/*
CHANGELOG

@ -0,0 +1,6 @@
/dist/*
/public/*
public/*
/dist*
/src/types/env.d.ts
/docs/**/*

@ -0,0 +1,3 @@
{
"recommendations": ["vue.volar", "lokalise.i18n-ally"]
}

@ -0,0 +1,19 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"prettier.enable": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"i18n-ally.localesPaths": ["src/locales"],
"i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true,
"i18n-ally.namespace": false,
"i18n-ally.enabledParsers": ["ts"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"],
"god.tsconfig": "./tsconfig.json"
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,13 @@
FROM node:18.0.0
WORKDIR /app
RUN npm install -g pnpm@8.1.0
COPY package.json .
RUN pnpm install
COPY . .
CMD [ "pnpm", "run", "dev" ]

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021-present Archer
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.

@ -0,0 +1,148 @@
<div align="center"> <a href="https://github.com/kailong321200875/vue-element-plus-admin"> <img width="100" src="./public/logo.png"> </a> <br> <br>
[![license](https://img.shields.io/github/license/kailong321200875/vue-element-plus-admin.svg)](LICENSE) [![repo-size](https://img.shields.io/github/repo-size/kailong321200875/vue-element-plus-admin.svg)](repo-size) [![last-commit](https://img.shields.io/github/last-commit/kailong321200875/vue-element-plus-admin.svg)](last-commit) [![stars](https://img.shields.io/github/stars/kailong321200875/vue-element-plus-admin.svg)](stars) [![forks](https://img.shields.io/github/forks/kailong321200875/vue-element-plus-admin.svg)](forks) [![release](https://img.shields.io/github/release/kailong321200875/vue-element-plus-admin.svg)](release) [![watchers](https://img.shields.io/github/watchers/kailong321200875/vue-element-plus-admin.svg)](watchers)
<h1>vue-element-plus-admin</h1>
</div>
**English** | [中文](./README.zh-CN.md)
## Introduction
vue-element-plus-admin is a free and open source middle and background template based on `element-plus`. Developed using the latest mainstream technologies such as `vue3`, `vite` and `typescript`, the out of the box middle and background front-end solution can be used as the starting template of the project and learning reference. And always pay attention to the latest technological trends and update them as soon as possible.
vue-element-plus-admin is positioned as a background integration scheme, which is not suitable for secondary development as a basic template. Because it integrates many functions that you may not use, it will cause a lot of code redundancy. If your project doesn't pay attention to this problem, you can also directly carry out secondary development based on it.
If you need a basic template, please switch to the `mini` branch. `mini` simply integrates some common layout functions such as layout and dynamic menu, which is more suitable for developers to carry out secondary development.
## Feature
- **State of The Art Development**Use front-end front-end technology development such as Vue3/vite4
- **TypeScript**: Application-level JavaScript language
- **Theming**: Configurable themes
- **International**Built-in complete internationalization program
- **Mock Server** Built-in mock data scheme
- **Authority** Built-in complete dynamic routing permission generation scheme.
- **Component** Multiple commonly used components are encapsulated twice
- **Examples** Built-in rich examples
## Preview
- [vue-element-plus-admin](https://element-plus-admin.cn/) - Full version of the github site
- [vue-element-plus-admin](https://kailong110120130.gitee.io/vue-element-plus-admin) - Full version of the gitee site
account: **admin/admin**
Online examples do not apply to menu filtering by default, but directly use Static routing
## Documentation
[Document Github](https://element-plus-admin-doc.cn/)
[Document Gitee](https://kailong110120130.gitee.io/vue-element-plus-admin-doc)
## Preparation
- [node](http://nodejs.org/) and [git](https://git-scm.com/) - Project development environment
- [Vite](https://vitejs.dev/) - Familiar with vite features
- [Vue3](https://v3.vuejs.org/) - Familiar with Vue basic syntax
- [TypeScript](https://www.typescriptlang.org/) - Familiar with the basic syntax of `TypeScript`
- [Es6+](http://es6.ruanyifeng.com/) - Familiar with es6 basic syntax
- [Vue-Router-Next](https://next.router.vuejs.org/) - Familiar with the basic use of vue-router
- [Element-Plus](https://element-plus.org/) - Familiar with the basic use of element-plus
- [Mock.js](https://github.com/nuysoft/Mock) - mockjs basic syntax
## Install and use
- Get the project code
```bash
git clone https://github.com/kailong321200875/vue-element-plus-admin.git
```
- Installation dependencies
```bash
cd vue-element-plus-admin
pnpm install
```
- run
```bash
pnpm run dev
```
- build
```bash
pnpm run build:pro
```
## Change Log
[CHANGELOG](./CHANGELOG.md)
## How to contribute
<a href="https://github.com/kailong321200875/vue-element-plus-admin/graphs/contributors">
<img src="https://contrib.rocks/image?repo=kailong321200875/vue-element-plus-admin" />
</a>
You can [Raise an issue](https://github.com/kailong321200875/vue-element-plus-admin/issues/new) Or submit a Pull Request.
**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`
## Git Contribution submission specification
- `feat` New features
- `fix` Fix bugs
- `docs` document
- `style` Format and style (changes that do not affect code operation)
- `refactor` Refactor
- `perf` Optimize related, such as improving performance and experience
- `test` Add test
- `build` Compilation related modifications, changes to project construction or dependencies
- `ci` Continuous integration modification
- `chore` Changes in the construction process or auxiliary tools
- `revert` Rollback to previous version
- `workflow` Workflow improvement
- `mod` Uncertain modification classification
- `wip` Under development
- `types` type
## Browser support
The `Chrome 80+` browser is recommended for local development
Support modern browsers, not IE
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| :-: | :-: | :-: | :-: | :-: |
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## Donate
If you find this project helpful, welcome sponsorship to show your support~
[Paypal Me](https://www.paypal.com/paypalme/ckl94)
<img src="https://github.com/kailong321200875/my-image/raw/master/pay.jpg" />
### My QR code
If you have any project cooperation or outsourcing, please scan the code to add me as a friend and leave a note of your purpose.
<img src="https://github.com/kailong321200875/my-image/raw/master/me.jpg" />
## License
[MIT](./LICENSE)

@ -0,0 +1,148 @@
<div align="center"> <a href="https://github.com/kailong321200875/vue-element-plus-admin"> <img width="100" src="./public/logo.png"> </a> <br> <br>
[![license](https://img.shields.io/github/license/kailong321200875/vue-element-plus-admin.svg)](LICENSE) [![repo-size](https://img.shields.io/github/repo-size/kailong321200875/vue-element-plus-admin.svg)](repo-size) [![last-commit](https://img.shields.io/github/last-commit/kailong321200875/vue-element-plus-admin.svg)](last-commit) [![stars](https://img.shields.io/github/stars/kailong321200875/vue-element-plus-admin.svg)](stars) [![forks](https://img.shields.io/github/forks/kailong321200875/vue-element-plus-admin.svg)](forks) [![release](https://img.shields.io/github/release/kailong321200875/vue-element-plus-admin.svg)](release) [![watchers](https://img.shields.io/github/watchers/kailong321200875/vue-element-plus-admin.svg)](watchers)
<h1>vue-element-plus-admin</h1>
</div>
[English](./README.md) | **中文**
## 介绍
vue-element-plus-admin 是一个基于 `element-plus` 免费开源的中后台模版。使用了最新的`vue3``vite``TypeScript`等主流技术开发,开箱即用的中后台前端解决方案,可以用来作为项目的启动模版,也可用于学习参考。并且时刻关注着最新技术动向,尽可能的第一时间更新。
vue-element-plus-admin 的定位是后台集成方案,不太适合当基础模板来进行二次开发。因为集成了很多你可能用不到的功能,会造成不少的代码冗余。如果你的项目不关注这方面的问题,也可以直接基于它进行二次开发。
如需要基础模版,请切换到 `mini` 分支,`mini` 只简单集成了一些如:布局、动态菜单等常用布局功能,更适合开发者进行二次开发。
## 特性
- **最新技术栈**:使用 Vue3/vite4 等前端前沿技术开发
- **TypeScript**: 应用程序级 JavaScript 的语言
- **主题**: 可配置的主题
- **国际化**:内置完善的国际化方案
- **自定义数据** 内置 Mock 数据方案
- **权限** 内置完善的动态路由权限生成方案
- **组件** 二次封装了多个常用的组件
- **示例** 内置丰富的示例
## 预览
- [vue-element-plus-admin](https://element-plus-admin.cn/) - 完整版 github 站点
- [vue-element-plus-admin](https://kailong110120130.gitee.io/vue-element-plus-admin) - 完整版 gitee 站点
帐号:**admin/admin**
在线例子默认不适用菜单过滤,而是直接使用静态路由表
## 文档
[文档地址 Github](https://element-plus-admin-doc.cn/)
[文档地址 Gitee](https://kailong110120130.gitee.io/vue-element-plus-admin-doc)
## 前序准备
- [node](http://nodejs.org/) 和 [git](https://git-scm.com/) - 项目开发环境
- [Vite](https://vitejs.dev/) - 熟悉 vite 特性
- [Vue3](https://v3.vuejs.org/) - 熟悉 Vue 基础语法
- [TypeScript](https://www.typescriptlang.org/) - 熟悉 `TypeScript` 基本语法
- [Es6+](http://es6.ruanyifeng.com/) - 熟悉 es6 基本语法
- [Vue-Router-Next](https://next.router.vuejs.org/) - 熟悉 vue-router 基本使用
- [Element-Plus](https://element-plus.org/) - element-plus 基本使用
- [Mock.js](https://github.com/nuysoft/Mock) - mockjs 基本语法
## 安装和使用
- 获取代码
```bash
git clone https://github.com/kailong321200875/vue-element-plus-admin.git
```
- 安装依赖
```bash
cd vue-element-plus-admin
pnpm install
```
- 运行
```bash
pnpm run dev
```
- 打包
```bash
pnpm run build:pro
```
## 更新日志
[更新日志](./CHANGELOG.md)
## 如何贡献
<a href="https://github.com/kailong321200875/vue-element-plus-admin/graphs/contributors">
<img src="https://contrib.rocks/image?repo=kailong321200875/vue-element-plus-admin" />
</a>
你可以[提一个 issue](https://github.com/kailong321200875/vue-element-plus-admin/issues/new) 或者提交一个 Pull Request。
**Pull Request:**
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`
## Git 贡献提交规范
- `feat` 新功能
- `fix` 修补 bug
- `docs` 文档
- `style` 格式、样式(不影响代码运行的变动)
- `refactor` 重构(即不是新增功能,也不是修改 BUG 的代码)
- `perf` 优化相关,比如提升性能、体验
- `test` 添加测试
- `build` 编译相关的修改,对项目构建或者依赖的改动
- `ci` 持续集成修改
- `chore` 构建过程或辅助工具的变动
- `revert` 回滚到上一个版本
- `workflow` 工作流改进
- `mod` 不确定分类的修改
- `wip` 开发中
- `types` 类型
## 浏览器支持
本地开发推荐使用 `Chrome 80+` 浏览器
支持现代浏览器, 不支持 IE
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| :-: | :-: | :-: | :-: | :-: |
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## Donate
如果你觉得这个项目有帮助,欢迎赞助以示支持~
[Paypal Me](https://www.paypal.com/paypalme/ckl94)
<img src="https://gitee.com/kailong110120130/my-image/raw/master/pay.jpg" />
### 我的二维码
如有项目合作或项目外包,扫码加我好友,请备注来意。
<img src="https://gitee.com/kailong110120130/my-image/raw/master/me.jpg" />
## 许可证
[MIT](./LICENSE)

@ -0,0 +1,28 @@
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'feat', // 新功能(feature)
'fix', // 修补bug
'docs', // 文档(documentation)
'style', // 格式、样式(不影响代码运行的变动)
'refactor', // 重构(即不是新增功能也不是修改BUG的代码)
'perf', // 优化相关,比如提升性能、体验
'test', // 添加测试
'ci', // 持续集成修改
'chore', // 构建过程或辅助工具的变动
'revert', // 回滚到上一个版本
'workflow', // 工作流改进
'mod', // 不确定分类的修改
'wip', // 开发中
'types', // 类型修改
'release' // 版本发布
]
],
'subject-full-stop': [0, 'never'],
'subject-case': [0, 'never']
}
}

@ -0,0 +1,10 @@
services:
vue-element-plus-admin:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "4000:4000"
volumes:
- /app/node_modules
- .:/app

@ -0,0 +1,82 @@
// 引入vue模版的eslint
import pluginVue from 'eslint-plugin-vue'
import eslint from '@eslint/js'
// ts-eslint解析器使 eslint 可以解析 ts 语法
import tseslint from 'typescript-eslint'
// vue文件解析器
import vueParser from 'vue-eslint-parser'
import prettier from 'eslint-plugin-prettier'
export default tseslint.config({
// ignores: ['node_modules', 'prettier.config.cjs', 'dist*'],
files: ['src/**/*.ts', 'src/**/*.tsx', 'src/**/*.vue'],
// tseslint.config添加了extends扁平函数直接用。否则是eslint9.0版本是没有extends的
extends: [
eslint.configs.recommended,
...tseslint.configs.recommended,
...pluginVue.configs['flat/essential']
],
plugins: {
prettier
},
languageOptions: {
parser: vueParser, // 使用vue解析器这个可以识别vue文件
parserOptions: {
parser: tseslint.parser, // 在vue文件上使用ts解析器
sourceType: 'module',
ecmaVersion: 2020,
ecmaFeatures: {
jsx: true
}
}
},
rules: {
'prettier/prettier': 'error',
'no-useless-escape': 0,
'no-undef': 0,
'@typescript-eslint/no-unused-expressions': 0,
'@typescript-eslint/no-unsafe-function-type': 0,
'vue/no-setup-props-destructure': 0,
'vue/script-setup-uses-vars': 1,
'vue/no-reserved-component-names': 0,
'@typescript-eslint/ban-ts-ignore': 0,
'@typescript-eslint/explicit-function-return-type': 0,
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/no-var-requires': 0,
'@typescript-eslint/no-empty-function': 0,
'vue/custom-event-name-casing': 0,
'no-use-before-define': 0,
'@typescript-eslint/no-use-before-define': 0,
'@typescript-eslint/ban-ts-comment': 0,
'@typescript-eslint/ban-types': 0,
'@typescript-eslint/no-non-null-assertion': 0,
'@typescript-eslint/explicit-module-boundary-types': 0,
'@typescript-eslint/no-unused-vars': 0,
'no-unused-vars': 0,
'space-before-function-paren': 0,
'vue/attributes-order': 0,
'vue/one-component-per-file': 0,
'vue/html-closing-bracket-newline': 0,
'vue/max-attributes-per-line': 0,
'vue/multiline-html-element-content-newline': 0,
'vue/singleline-html-element-content-newline': 0,
'vue/attribute-hyphenation': 0,
'vue/require-default-prop': 0,
'vue/require-explicit-emits': 0,
'vue/html-self-closing': [
1,
{
html: {
void: 'always',
normal: 'never',
component: 'always'
},
svg: 'always',
math: 'always'
}
],
'vue/multi-word-component-names': 0,
'vue/no-v-html': 0,
'vue/require-toggle-inside-transition': 0
}
})

@ -0,0 +1,142 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= title %></title>
</head>
<body>
<div id="app">
<style>
.app-loading {
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
flex-direction: column;
background: #f0f2f5;
}
.app-loading .app-loading-wrap {
position: absolute;
top: 50%;
left: 50%;
display: flex;
-webkit-transform: translate3d(-50%, -50%, 0);
transform: translate3d(-50%, -50%, 0);
justify-content: center;
align-items: center;
flex-direction: column;
}
.app-loading .app-loading-title {
margin-bottom: 30px;
font-size: 20px;
font-weight: bold;
text-align: center;
}
.app-loading .app-loading-logo {
width: 100px;
margin: 0 auto 15px auto;
}
.app-loading .app-loading-item {
position: relative;
display: inline-block;
width: 60px;
height: 60px;
vertical-align: middle;
border-radius: 50%;
}
.app-loading .app-loading-outter {
position: absolute;
width: 100%;
height: 100%;
border: 4px solid #2d8cf0;
border-bottom: 0;
border-left-color: transparent;
border-radius: 50%;
animation: loader-outter 1s cubic-bezier(0.42, 0.61, 0.58, 0.41) infinite;
}
.app-loading .app-loading-inner {
position: absolute;
top: calc(50% - 20px);
left: calc(50% - 20px);
width: 40px;
height: 40px;
border: 4px solid #87bdff;
border-right: 0;
border-top-color: transparent;
border-radius: 50%;
animation: loader-inner 1s cubic-bezier(0.42, 0.61, 0.58, 0.41) infinite;
}
@-webkit-keyframes loader-outter {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes loader-outter {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@-webkit-keyframes loader-inner {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(-360deg);
transform: rotate(-360deg);
}
}
@keyframes loader-inner {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(-360deg);
transform: rotate(-360deg);
}
}
</style>
<div class="app-loading">
<div class="app-loading-wrap">
<div class="app-loading-title">
<img src="/logo.png" class="app-loading-logo" alt="Logo" />
<div class="app-loading-title"><%= title %></div>
</div>
<div class="app-loading-item">
<div class="app-loading-outter"></div>
<div class="app-loading-inner"></div>
</div>
</div>
</div>
</div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

@ -0,0 +1,18 @@
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
const modules = import.meta.glob('./**/*.mock.ts', {
import: 'default',
eager: true
})
const mockModules: any[] = []
Object.keys(modules).forEach(async (key) => {
if (key.includes('_')) {
return
}
mockModules.push(...(modules[key] as any))
})
export function setupProdMockServer() {
createProdMockServer(mockModules)
}

@ -0,0 +1,87 @@
import { SUCCESS_CODE } from '@/constants'
import { MockMethod } from 'vite-plugin-mock'
const timeout = 1000
export default [
// 分析页统计接口
{
url: '/mock/analysis/total',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: {
users: 102400,
messages: 81212,
moneys: 9280,
shoppings: 13600
}
}
}
},
// 用户来源
{
url: '/mock/analysis/userAccessSource',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: [
{ value: 1000, name: 'analysis.directAccess' },
{ value: 310, name: 'analysis.mailMarketing' },
{ value: 234, name: 'analysis.allianceAdvertising' },
{ value: 135, name: 'analysis.videoAdvertising' },
{ value: 1548, name: 'analysis.searchEngines' }
]
}
}
},
// 每周用户活跃量
{
url: '/mock/analysis/weeklyUserActivity',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: [
{ value: 13253, name: 'analysis.monday' },
{ value: 34235, name: 'analysis.tuesday' },
{ value: 26321, name: 'analysis.wednesday' },
{ value: 12340, name: 'analysis.thursday' },
{ value: 24643, name: 'analysis.friday' },
{ value: 1322, name: 'analysis.saturday' },
{ value: 1324, name: 'analysis.sunday' }
]
}
}
},
// 每月销售额
{
url: '/mock/analysis/monthlySales',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: [
{ estimate: 100, actual: 120, name: 'analysis.january' },
{ estimate: 120, actual: 82, name: 'analysis.february' },
{ estimate: 161, actual: 91, name: 'analysis.march' },
{ estimate: 134, actual: 154, name: 'analysis.april' },
{ estimate: 105, actual: 162, name: 'analysis.may' },
{ estimate: 160, actual: 140, name: 'analysis.june' },
{ estimate: 165, actual: 145, name: 'analysis.july' },
{ estimate: 114, actual: 250, name: 'analysis.august' },
{ estimate: 163, actual: 134, name: 'analysis.september' },
{ estimate: 185, actual: 56, name: 'analysis.october' },
{ estimate: 118, actual: 99, name: 'analysis.november' },
{ estimate: 123, actual: 123, name: 'analysis.december' }
]
}
}
}
] as MockMethod[]

@ -0,0 +1,206 @@
import { toAnyString } from '@/utils'
import Mock from 'mockjs'
import { SUCCESS_CODE } from '@/constants'
const departmentList: any = []
const citys = ['厦门总公司', '北京分公司', '上海分公司', '福州分公司', '深圳分公司', '杭州分公司']
for (let i = 0; i < 5; i++) {
departmentList.push({
// 部门名称
departmentName: citys[i],
id: toAnyString(),
createTime: '@datetime',
// 状态
status: Mock.Random.integer(0, 1),
// 备注
remark: '@cword(10, 15)',
children: [
{
// 部门名称
departmentName: '研发部',
id: toAnyString(),
createTime: '@datetime',
// 状态
status: Mock.Random.integer(0, 1),
// 备注
remark: '@cword(10, 15)'
},
{
// 部门名称
departmentName: '产品部',
id: toAnyString(),
createTime: '@datetime',
// 状态
status: Mock.Random.integer(0, 1),
// 备注
remark: '@cword(10, 15)'
},
{
// 部门名称
departmentName: '运营部',
id: toAnyString(),
createTime: '@datetime',
// 状态
status: Mock.Random.integer(0, 1),
// 备注
remark: '@cword(10, 15)'
},
{
// 部门名称
departmentName: '市场部',
id: toAnyString(),
createTime: '@datetime',
// 状态
status: Mock.Random.integer(0, 1),
// 备注
remark: '@cword(10, 15)'
},
{
// 部门名称
departmentName: '销售部',
id: toAnyString(),
createTime: '@datetime',
// 状态
status: Mock.Random.integer(0, 1),
// 备注
remark: '@cword(10, 15)'
},
{
// 部门名称
departmentName: '客服部',
id: toAnyString(),
createTime: '@datetime',
// 状态
status: Mock.Random.integer(0, 1),
// 备注
remark: '@cword(10, 15)'
}
]
})
}
export default [
// 列表接口
{
url: '/mock/department/list',
method: 'get',
response: () => {
return {
code: SUCCESS_CODE,
data: {
list: departmentList
}
}
}
},
{
url: '/mock/department/table/list',
method: 'get',
response: () => {
return {
code: SUCCESS_CODE,
data: {
list: departmentList,
total: 5
}
}
}
},
{
url: '/mock/department/users',
method: 'get',
timeout: 1000,
response: ({ query }) => {
const { pageSize } = query
// 根据pageSize来创建数据
const mockList: any = []
for (let i = 0; i < pageSize; i++) {
mockList.push(
Mock.mock({
// 用户名
username: '@cname',
// 账号
account: '@first',
// 邮箱
email: '@EMAIL',
// 创建时间
createTime: '@datetime',
// 用户id
id: toAnyString()
})
)
}
return {
code: SUCCESS_CODE,
data: {
total: 100,
list: mockList
}
}
}
},
// 保存接口
{
url: '/mock/department/user/save',
method: 'post',
timeout: 1000,
response: () => {
return {
code: SUCCESS_CODE,
data: 'success'
}
}
},
// 删除接口
{
url: '/mock/department/user/delete',
method: 'post',
response: ({ body }) => {
const ids = body.ids
if (!ids) {
return {
code: 500,
message: '请选择需要删除的数据'
}
} else {
return {
code: SUCCESS_CODE,
data: 'success'
}
}
}
},
// 保存接口
{
url: '/mock/department/save',
method: 'post',
timeout: 1000,
response: () => {
return {
code: SUCCESS_CODE,
data: 'success'
}
}
},
// 删除接口
{
url: '/mock/department/delete',
method: 'post',
response: ({ body }) => {
const ids = body.ids
if (!ids) {
return {
code: 500,
message: '请选择需要删除的数据'
}
} else {
return {
code: SUCCESS_CODE,
data: 'success'
}
}
}
}
]

@ -0,0 +1,60 @@
import { SUCCESS_CODE } from '@/constants'
const timeout = 1000
const dictObj: Recordable = {
importance: [
{
value: 0,
label: 'tableDemo.commonly'
},
{
value: 1,
label: 'tableDemo.good'
},
{
value: 2,
label: 'tableDemo.important'
}
]
}
export default [
// 字典接口
{
url: '/mock/dict/list',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: dictObj
}
}
},
// 获取某个字典
{
url: '/mock/dict/one',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: [
{
label: 'test1',
value: 0
},
{
label: 'test2',
value: 1
},
{
label: 'test3',
value: 2
}
]
}
}
}
]

@ -0,0 +1,357 @@
import Mock from 'mockjs'
import { SUCCESS_CODE } from '@/constants'
const timeout = 1000
export default [
// 列表接口
{
url: '/mock/menu/list',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: {
list: [
{
path: '/dashboard',
component: '#',
redirect: '/dashboard/analysis',
name: 'Dashboard',
status: Mock.Random.integer(0, 1),
id: 1,
type: 0,
parentId: undefined,
title: '首页',
meta: {
title: '首页',
icon: 'vi-ant-design:dashboard-filled',
alwaysShow: true
},
children: [
{
path: 'analysis',
component: 'views/Dashboard/Analysis',
name: 'Analysis',
status: Mock.Random.integer(0, 1),
id: 2,
type: 1,
parentId: 1,
title: '分析页',
permissionList: [
{
id: 1,
label: '新增',
value: 'add'
},
{
id: 2,
label: '编辑',
value: 'edit'
}
],
meta: {
title: '分析页',
noCache: true,
permission: ['add', 'edit']
}
},
{
path: 'workplace',
component: 'views/Dashboard/Workplace',
name: 'Workplace',
status: Mock.Random.integer(0, 1),
id: 3,
type: 1,
parentId: 1,
title: '工作台',
permissionList: [
{
id: 1,
label: '新增',
value: 'add'
},
{
id: 2,
label: '编辑',
value: 'edit'
},
{
id: 3,
label: '删除',
value: 'delete'
}
],
meta: {
title: '工作台',
noCache: true
}
}
]
},
{
path: '/external-link',
component: '#',
meta: {
title: '文档',
icon: 'vi-clarity:document-solid'
},
name: 'ExternalLink',
status: Mock.Random.integer(0, 1),
id: 4,
type: 0,
parentId: undefined,
title: '文档',
children: [
{
path: 'https://element-plus-admin-doc.cn/',
name: 'DocumentLink',
status: Mock.Random.integer(0, 1),
id: 5,
type: 1,
parentId: 4,
title: '文档',
meta: {
title: '文档'
}
}
]
},
{
path: '/level',
component: '#',
redirect: '/level/menu1/menu1-1/menu1-1-1',
name: 'Level',
status: Mock.Random.integer(0, 1),
id: 6,
type: 0,
parentId: undefined,
title: '菜单',
meta: {
title: '菜单',
icon: 'vi-carbon:skill-level-advanced'
},
children: [
{
path: 'menu1',
name: 'Menu1',
component: '##',
status: Mock.Random.integer(0, 1),
id: 7,
type: 0,
parentId: 6,
title: '菜单1',
redirect: '/level/menu1/menu1-1/menu1-1-1',
meta: {
title: '菜单1'
},
children: [
{
path: 'menu1-1',
name: 'Menu11',
component: '##',
status: Mock.Random.integer(0, 1),
id: 8,
type: 0,
parentId: 7,
title: '菜单1-1',
redirect: '/level/menu1/menu1-1/menu1-1-1',
meta: {
title: '菜单1-1',
alwaysShow: true
},
children: [
{
path: 'menu1-1-1',
name: 'Menu111',
component: 'views/Level/Menu111',
status: Mock.Random.integer(0, 1),
id: 9,
type: 1,
parentId: 8,
title: '菜单1-1-1',
meta: {
title: '菜单1-1-1'
}
}
]
},
{
path: 'menu1-2',
name: 'Menu12',
component: 'views/Level/Menu12',
status: Mock.Random.integer(0, 1),
id: 10,
type: 1,
parentId: 7,
title: '菜单1-2',
meta: {
title: '菜单1-2'
}
}
]
},
{
path: 'menu2',
name: 'Menu2Demo',
component: 'views/Level/Menu2',
status: Mock.Random.integer(0, 1),
id: 11,
type: 1,
parentId: 6,
title: '菜单2',
meta: {
title: '菜单2'
}
}
]
},
{
path: '/example',
component: '#',
redirect: '/example/example-dialog',
name: 'Example',
status: Mock.Random.integer(0, 1),
id: 12,
type: 0,
parentId: undefined,
title: '综合示例',
meta: {
title: '综合示例',
icon: 'vi-ep:management',
alwaysShow: true
},
children: [
{
path: 'example-dialog',
component: 'views/Example/Dialog/ExampleDialog',
name: 'ExampleDialog',
status: Mock.Random.integer(0, 1),
id: 13,
type: 1,
parentId: 12,
title: '综合示例-弹窗',
permissionList: [
{
id: 1,
label: '新增',
value: 'add'
},
{
id: 2,
label: '编辑',
value: 'edit'
},
{
id: 3,
label: '删除',
value: 'delete'
},
{
id: 4,
label: '查看',
value: 'view'
}
],
meta: {
title: '综合示例-弹窗'
}
},
{
path: 'example-page',
component: 'views/Example/Page/ExamplePage',
name: 'ExamplePage',
status: Mock.Random.integer(0, 1),
id: 14,
type: 1,
parentId: 12,
title: '综合示例-页面',
permissionList: [
{
id: 1,
label: '新增',
value: 'add'
},
{
id: 2,
label: '编辑',
value: 'edit'
},
{
id: 3,
label: '删除',
value: 'delete'
},
{
id: 4,
label: '查看',
value: 'view'
}
],
meta: {
title: '综合示例-页面'
}
},
{
path: 'example-add',
component: 'views/Example/Page/ExampleAdd',
name: 'ExampleAdd',
status: Mock.Random.integer(0, 1),
id: 15,
type: 1,
parentId: 12,
title: '综合示例-新增',
meta: {
title: '综合示例-新增',
noTagsView: true,
noCache: true,
hidden: true,
showMainRoute: true,
activeMenu: '/example/example-page'
}
},
{
path: 'example-edit',
component: 'views/Example/Page/ExampleEdit',
name: 'ExampleEdit',
status: Mock.Random.integer(0, 1),
id: 16,
type: 1,
parentId: 12,
title: '综合示例-编辑',
meta: {
title: '综合示例-编辑',
noTagsView: true,
noCache: true,
hidden: true,
showMainRoute: true,
activeMenu: '/example/example-page'
}
},
{
path: 'example-detail',
component: 'views/Example/Page/ExampleDetail',
name: 'ExampleDetail',
status: Mock.Random.integer(0, 1),
id: 17,
type: 1,
parentId: 12,
title: '综合示例-详情',
meta: {
title: '综合示例-详情',
noTagsView: true,
noCache: true,
hidden: true,
showMainRoute: true,
activeMenu: '/example/example-page'
}
}
]
}
]
}
}
}
}
]

@ -0,0 +1,72 @@
import { SUCCESS_CODE } from '@/constants'
const timeout = 600000
export default [
{
url: '/mock/request/1',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: 'request-1'
}
}
},
{
url: '/mock/request/2',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: 'request-2'
}
}
},
{
url: '/mock/request/3',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: 'request-3'
}
}
},
{
url: '/mock/request/4',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: 'request-4'
}
}
},
{
url: '/mock/request/5',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: 'request-5'
}
}
},
{
url: '/mock/request/expired',
method: 'get',
timeout: 0,
response: () => {
return {
code: 401,
message: 'token expired'
}
}
}
]

File diff suppressed because it is too large Load Diff

@ -0,0 +1,319 @@
import Mock from 'mockjs'
import { SUCCESS_CODE } from '@/constants'
import { toAnyString } from '@/utils'
const timeout = 1000
const count = 100
const baseContent =
'<p>I am testing data, I am testing data.</p><p><img src="https://wpimg.wallstcn.com/4c69009c-0fd4-4153-b112-6cb53d1cf943"></p>'
interface ListProps {
id: string
author: string
title: string
content: string
importance: number
display_time: any
pageviews: number
image_uri: string
video_uri?: string
}
interface TreeListProps {
id: string
author: string
title: string
content: string
importance: number
display_time: any
image_uri: string
pageviews: number
video_uri?: string
children?: TreeListProps[]
}
let List: ListProps[] = []
for (let i = 0; i < count; i++) {
List.push(
Mock.mock({
id: toAnyString(),
// timestamp: +Mock.Random.date('T'),
author: '@first',
title: '@title(5, 10)',
content: baseContent,
importance: '@integer(1, 3)',
display_time: '@datetime',
pageviews: '@integer(100, 500)',
image_uri: Mock.Random.image('@integer(100, 500)x@integer(100, 500)'),
video_uri:
'//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-720p.mp4'
})
)
}
const treeList: TreeListProps[] = []
for (let i = 0; i < count; i++) {
treeList.push(
Mock.mock({
id: toAnyString(),
// timestamp: +Mock.Random.date('T'),
author: '@first',
title: '@title(5, 10)',
content: baseContent,
importance: '@integer(1, 3)',
display_time: '@datetime',
pageviews: '@integer(300, 5000)',
image_uri: Mock.Random.image('@integer(100, 500)x@integer(100, 500)'),
children: [
{
id: toAnyString(),
// timestamp: +Mock.Random.date('T'),
author: '@first',
title: '@title(5, 10)',
content: baseContent,
importance: '@integer(1, 3)',
display_time: '@datetime',
pageviews: '@integer(300, 5000)',
image_uri: Mock.Random.image('@integer(100, 500)x@integer(100, 500)'),
children: [
{
id: toAnyString(),
// timestamp: +Mock.Random.date('T'),
author: '@first',
title: '@title(5, 10)',
content: baseContent,
importance: '@integer(1, 3)',
display_time: '@datetime',
pageviews: '@integer(300, 5000)',
image_uri: Mock.Random.image('@integer(100, 500)x@integer(100, 500)')
},
{
id: toAnyString(),
// timestamp: +Mock.Random.date('T'),
author: '@first',
title: '@title(5, 10)',
content: baseContent,
importance: '@integer(1, 3)',
display_time: '@datetime',
pageviews: '@integer(300, 5000)',
image_uri: Mock.Random.image('@integer(100, 500)x@integer(100, 500)')
}
]
},
{
id: toAnyString(),
// timestamp: +Mock.Random.date('T'),
author: '@first',
title: '@title(5, 10)',
content: baseContent,
importance: '@integer(1, 3)',
display_time: '@datetime',
pageviews: '@integer(300, 5000)',
image_uri: Mock.Random.image('@integer(100, 500)x@integer(100, 500)')
},
{
id: toAnyString(),
// timestamp: +Mock.Random.date('T'),
author: '@first',
title: '@title(5, 10)',
content: baseContent,
importance: '@integer(1, 3)',
display_time: '@datetime',
pageviews: '@integer(300, 5000)',
image_uri: Mock.Random.image('@integer(100, 500)x@integer(100, 500)')
},
{
id: toAnyString(),
// timestamp: +Mock.Random.date('T'),
author: '@first',
title: '@title(5, 10)',
content: baseContent,
importance: '@integer(1, 3)',
display_time: '@datetime',
pageviews: '@integer(300, 5000)',
image_uri: Mock.Random.image('@integer(100, 500)x@integer(100, 500)')
}
]
// image_uri
})
)
}
const cardList = [
{
logo: 'https://gw.alipayobjects.com/zos/rmsportal/WdGqmHpayyMjiEhcKoVE.png',
name: 'Alipay',
desc: '在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。'
},
{
logo: 'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png',
name: 'Angular',
desc: '在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。'
},
{
logo: 'https://gw.alipayobjects.com/zos/rmsportal/siCrBXXhmvTQGWPNLBow.png',
name: 'Bootstrap',
desc: '在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。'
},
{
logo: 'https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png',
name: 'React',
desc: '在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。'
},
{
logo: 'https://gw.alipayobjects.com/zos/rmsportal/ComBAopevLwENQdKWiIn.png',
name: 'Vue',
desc: '在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。'
},
{
logo: 'https://gw.alipayobjects.com/zos/rmsportal/nxkuOJlFJuAUhzlMTCEe.png',
name: 'Webpack',
desc: '在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。'
}
]
export default [
// 树形列表接口
{
url: '/mock/example/treeList',
method: 'get',
timeout,
response: ({ query }) => {
const { title, pageIndex, pageSize } = query
const mockList = treeList.filter((item) => {
if (title && item.title.indexOf(title) < 0) return false
return true
})
const pageList = mockList.filter(
(_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1)
)
return {
code: SUCCESS_CODE,
data: {
total: mockList.length,
list: pageList
}
}
}
},
// 列表接口
{
url: '/mock/example/list',
method: 'get',
timeout,
response: ({ query }) => {
const { title, pageIndex, pageSize } = query
const mockList = List.filter((item) => {
if (title && item.title.indexOf(title) < 0) return false
return true
})
const pageList = mockList.filter(
(_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1)
)
return {
code: SUCCESS_CODE,
data: {
total: mockList.length,
list: pageList
}
}
}
},
// 保存接口
{
url: '/mock/example/save',
method: 'post',
timeout,
response: ({ body }) => {
if (!body.id) {
List = [
Object.assign(body, {
id: toAnyString()
})
].concat(List)
return {
code: SUCCESS_CODE,
data: 'success'
}
} else {
List.map((item) => {
if (item.id === body.id) {
for (const key in item) {
item[key] = body[key]
}
}
})
return {
code: SUCCESS_CODE,
data: 'success'
}
}
}
},
// 详情接口
{
url: '/mock/example/detail',
method: 'get',
response: ({ query }) => {
const { id } = query
for (const example of List) {
if (example.id === id) {
return {
code: SUCCESS_CODE,
data: example
}
}
}
}
},
// 删除接口
{
url: '/mock/example/delete',
method: 'post',
response: ({ body }) => {
const ids = body.ids
if (!ids) {
return {
code: 500,
message: '请选择需要删除的数据'
}
} else {
let i = List.length
while (i--) {
if (ids.indexOf(List[i].id) !== -1) {
List.splice(i, 1)
}
}
return {
code: SUCCESS_CODE,
data: 'success'
}
}
}
},
{
url: '/mock/card/list',
method: 'get',
timeout,
response: ({ query }) => {
const { name, pageIndex, pageSize } = query
const mockList = cardList.filter((item) => {
if (name && item.name.indexOf(name) < 0) return false
return true
})
const pageList = mockList.filter(
(_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1)
)
return {
code: SUCCESS_CODE,
data: {
total: mockList.length,
list: pageList
}
}
}
}
]

@ -0,0 +1,90 @@
import { SUCCESS_CODE } from '@/constants'
const timeout = 1000
const List: {
username: string
password: string
role: string
roleId: string
permissions: string | string[]
}[] = [
{
username: 'admin',
password: 'admin',
role: 'admin',
roleId: '1',
permissions: ['*.*.*']
},
{
username: 'test',
password: 'test',
role: 'test',
roleId: '2',
permissions: ['example:dialog:create', 'example:dialog:delete']
}
]
export default [
// 列表接口
{
url: '/mock/user/list',
method: 'get',
response: ({ query }) => {
const { username, pageIndex, pageSize } = query
const mockList = List.filter((item) => {
if (username && item.username.indexOf(username) < 0) return false
return true
})
const pageList = mockList.filter(
(_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1)
)
return {
code: SUCCESS_CODE,
data: {
total: mockList.length,
list: pageList
}
}
}
},
// 登录接口
{
url: '/mock/user/login',
method: 'post',
timeout,
response: ({ body }) => {
const data = body
let hasUser = false
for (const user of List) {
if (user.username === data.username && user.password === data.password) {
hasUser = true
return {
code: SUCCESS_CODE,
data: user
}
}
}
if (!hasUser) {
return {
code: 500,
message: '账号或密码错误'
}
}
}
},
// 退出接口
{
url: '/mock/user/loginOut',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: null
}
}
}
]

@ -0,0 +1,169 @@
import { SUCCESS_CODE } from '@/constants'
const timeout = 1000
export default [
// 获取统计
{
url: '/mock/workplace/total',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: {
project: 40,
access: 2340,
todo: 10
}
}
}
},
// 获取项目
{
url: '/mock/workplace/project',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: [
{
name: 'Github',
icon: 'akar-icons:github-fill',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'Vue',
icon: 'logos:vue',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'Angular',
icon: 'logos:angular-icon',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'React',
icon: 'logos:react',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'Webpack',
icon: 'logos:webpack',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'Vite',
icon: 'vscode-icons:file-type-vite',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
}
]
}
}
},
// 获取动态
{
url: '/mock/workplace/dynamic',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: [
{
keys: ['workplace.push', 'Github'],
time: new Date()
},
{
keys: ['workplace.push', 'Github'],
time: new Date()
},
{
keys: ['workplace.push', 'Github'],
time: new Date()
},
{
keys: ['workplace.push', 'Github'],
time: new Date()
},
{
keys: ['workplace.push', 'Github'],
time: new Date()
},
{
keys: ['workplace.push', 'Github'],
time: new Date()
}
]
}
}
},
// 获取团队信息
{
url: '/mock/workplace/team',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: [
{
name: 'Github',
icon: 'akar-icons:github-fill'
},
{
name: 'Vue',
icon: 'logos:vue'
},
{
name: 'Angular',
icon: 'logos:angular-icon'
},
{
name: 'React',
icon: 'logos:react'
},
{
name: 'Webpack',
icon: 'logos:webpack'
},
{
name: 'Vite',
icon: 'vscode-icons:file-type-vite'
}
]
}
}
},
// 获取指数
{
url: '/mock/workplace/radar',
method: 'get',
timeout,
response: () => {
return {
code: SUCCESS_CODE,
data: [
{ name: 'workplace.quote', max: 65, personal: 42, team: 50 },
{ name: 'workplace.contribution', max: 160, personal: 30, team: 140 },
{ name: 'workplace.hot', max: 300, personal: 20, team: 28 },
{ name: 'workplace.yield', max: 130, personal: 35, team: 35 },
{ name: 'workplace.follow', max: 100, personal: 80, team: 90 }
]
}
}
}
]

@ -0,0 +1,139 @@
{
"name": "vue-element-plus-admin",
"version": "2.9.0",
"description": "一套基于vue3、element-plus、typesScript、vite4的后台集成方案。",
"author": "Archer <502431556@qq.com>",
"private": false,
"type": "module",
"scripts": {
"i": "pnpm install",
"dev": "pnpm vite --mode base",
"ts:check": "pnpm vue-tsc --noEmit --skipLibCheck",
"build:pro": "pnpm vite build --mode pro",
"build:gitee": "pnpm vite build --mode gitee",
"build:dev": "pnpm vite build --mode dev",
"build:test": "pnpm vite build --mode test",
"serve:pro": "pnpm vite preview --mode pro",
"serve:dev": "pnpm vite preview --mode dev",
"serve:test": "pnpm vite preview --mode test",
"npm:check": "pnpx npm-check-updates -u",
"clean": "pnpx rimraf node_modules",
"clean:cache": "pnpx rimraf node_modules/.cache",
"lint:eslint": "eslint . --fix \"src/**/*.{js,ts,tsx,vue,html}\"",
"lint:format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,vue,html,md}\"",
"lint:style": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.cjs",
"prepare": "husky install",
"p": "plop",
"icon": "esno ./scripts/icon.ts"
},
"dependencies": {
"@iconify/iconify": "^3.1.1",
"@iconify/vue": "^4.3.0",
"@vueuse/core": "^12.3.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.10",
"@zxcvbn-ts/core": "^3.0.4",
"animate.css": "^4.1.1",
"axios": "^1.7.9",
"cropperjs": "^1.6.2",
"dayjs": "^1.11.13",
"driver.js": "^1.3.1",
"echarts": "^5.6.0",
"echarts-wordcloud": "^2.1.0",
"element-plus": "2.9.2",
"lodash-es": "^4.17.21",
"mitt": "^3.0.1",
"monaco-editor": "^0.52.2",
"nprogress": "^0.2.0",
"pinia": "^2.3.0",
"pinia-plugin-persistedstate": "^4.2.0",
"qrcode": "^1.5.4",
"qs": "^6.13.1",
"url": "^0.11.4",
"vue": "3.5.13",
"vue-draggable-plus": "^0.6.0",
"vue-i18n": "11.0.1",
"vue-json-pretty": "^2.4.0",
"vue-router": "^4.5.0",
"vue-types": "^5.1.3",
"xgplayer": "^3.0.20"
},
"devDependencies": {
"@commitlint/cli": "^19.6.1",
"@commitlint/config-conventional": "^19.6.0",
"@iconify/json": "^2.2.293",
"@intlify/unplugin-vue-i18n": "^6.0.3",
"@types/fs-extra": "^11.0.4",
"@types/inquirer": "^9.0.7",
"@types/lodash-es": "^4.17.12",
"@types/mockjs": "^1.0.10",
"@types/node": "^22.10.5",
"@types/nprogress": "^0.2.3",
"@types/qrcode": "^1.5.5",
"@types/qs": "^6.9.17",
"@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^8.19.1",
"@typescript-eslint/parser": "^8.19.1",
"@unocss/transformer-variant-group": "^0.65.4",
"@vitejs/plugin-legacy": "^6.0.0",
"@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^4.1.1",
"autoprefixer": "^10.4.20",
"chalk": "^5.4.1",
"consola": "^3.3.3",
"eslint": "^9.17.0",
"eslint-config-prettier": "^9.1.0",
"eslint-define-config": "^2.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-vue": "^9.32.0",
"esno": "^4.8.0",
"fs-extra": "^11.2.0",
"husky": "^9.1.7",
"inquirer": "^12.3.0",
"less": "^4.2.1",
"lint-staged": "^15.3.0",
"mockjs": "^1.1.0",
"plop": "^4.0.1",
"postcss": "^8.4.49",
"postcss-html": "^1.7.0",
"postcss-less": "^6.0.0",
"prettier": "^3.4.2",
"rimraf": "^6.0.1",
"rollup": "^4.30.1",
"rollup-plugin-visualizer": "^5.14.0",
"stylelint": "^16.12.0",
"stylelint-config-html": "^1.1.0",
"stylelint-config-recommended": "^14.0.1",
"stylelint-config-standard": "^36.0.1",
"stylelint-order": "^6.0.4",
"terser": "^5.37.0",
"typescript": "5.7.3",
"typescript-eslint": "^8.19.1",
"unocss": "^0.65.4",
"vite": "6.0.7",
"vite-plugin-ejs": "^1.7.0",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-mock": "2.9.6",
"vite-plugin-progress": "^0.0.7",
"vite-plugin-purge-icons": "^0.10.0",
"vite-plugin-style-import": "2.0.0",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-url-copy": "^1.1.4",
"vue-tsc": "^2.2.0"
},
"packageManager": "pnpm@9.15.3",
"engines": {
"node": ">=18.0.0",
"pnpm": ">=8.1.0"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/kailong321200875/vue-element-plus-admin.git"
},
"bugs": {
"url": "https://github.com/kailong321200875/vue-element-plus-admin/issues"
},
"homepage": "https://github.com/kailong321200875/vue-element-plus-admin"
}

@ -0,0 +1,11 @@
<script setup lang="ts">
import { useDesign } from '@/hooks/web/useDesign'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('{{ name }}')
</script>
<template>
<div :class="`${prefixCls}-{{ name }}`">{{ upperFirstName }}</div>
</template>

@ -0,0 +1,3 @@
import {{ upperFirstName }} from './src/{{ upperFirstName }}.vue'
export { {{ upperFirstName }} }

@ -0,0 +1,38 @@
const toUpperCase = (str) => str.charAt(0).toUpperCase() + str.slice(1)
module.exports = {
description: 'Create vue component',
prompts: [
{
type: 'input',
name: 'name',
message: '请输入组件名称Please enter the component name'
}
],
actions: (data) => {
const { name } = data
const upperFirstName = toUpperCase(name)
const actions = []
if (name) {
actions.push({
type: 'add',
path: `./src/components/${upperFirstName}/src/${upperFirstName}.vue`,
templateFile: './plop/component/component.hbs',
data: {
name,
upperFirstName
}
}, {
type: 'add',
path: `./src/components/${upperFirstName}/index.ts`,
templateFile: './plop/component/index.hbs',
data: {
upperFirstName
}
})
}
return actions
}
}

@ -0,0 +1,37 @@
const toUpperCase = (str) => str.charAt(0).toUpperCase() + str.slice(1)
module.exports = {
description: 'Create vue view',
prompts: [
{
type: 'input',
name: 'path',
message: '请输入路径Please enter a path',
default: 'views'
},
{
type: 'input',
name: 'name',
message: '请输入模块名称Please enter module name'
}
],
actions: (data) => {
const { name, path } = data
const upperFirstName = toUpperCase(name)
const actions = []
if (name) {
actions.push({
type: 'add',
path: `./src/${path}/${upperFirstName}.vue`,
templateFile: './plop/view/view.hbs',
data: {
name,
upperFirstName
}
})
}
return actions
}
}

@ -0,0 +1,7 @@
<script setup lang="ts">
import { ContentWrap } from '@/components/ContentWrap'
</script>
<template>
<ContentWrap title="{{ upperFirstName }}"> {{ name }} </ContentWrap>
</template>

@ -0,0 +1,7 @@
const viewGenerator = require('./plop/view/prompt.cjs')
const componentGenerator = require('./plop/component/prompt.cjs')
module.exports = function (plop) {
plop.setGenerator('view', viewGenerator)
plop.setGenerator('component', componentGenerator)
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,5 @@
module.exports = {
plugins: {
autoprefixer: {}
}
}

@ -0,0 +1,19 @@
module.exports = {
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: false,
vueIndentScriptAndStyle: false,
singleQuote: true,
quoteProps: 'as-needed',
bracketSpacing: true,
trailingComma: 'none',
jsxSingleQuote: false,
arrowParens: 'always',
insertPragma: false,
requirePragma: false,
proseWrap: 'never',
htmlWhitespaceSensitivity: 'strict',
endOfLine: 'auto',
rangeStart: 0
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

@ -0,0 +1,73 @@
import path from 'path'
import fs from 'fs-extra'
import inquirer from 'inquirer'
import chalk from 'chalk'
import pkg from '../package.json'
import { ICON_PREFIX } from '../src/constants'
interface Icon {
name: string
prefix: string
icons: string[]
}
async function generateIcon() {
const dir = path.resolve(process.cwd(), 'node_modules/@iconify/json')
const raw = await fs.readJSON(path.join(dir, 'collections.json'))
const collections = Object.entries(raw).map(([id, v]) => ({
...(v as any),
id
}))
const choices = collections.map((item) => ({ key: item.id, value: item.id, name: item.name }))
inquirer
.prompt([
// {
// type: 'list',
// name: 'useType',
// choices: [
// { key: 'local', value: 'local', name: 'Local' },
// { key: 'onLine', value: 'onLine', name: 'OnLine' }
// ],
// message: 'How to use icons?'
// },
{
type: 'list',
name: 'iconSet',
choices: choices,
message: 'Select the icon set that needs to be generated?'
}
])
// ↓命令行问答的答案
.then(async (answers) => {
const { iconSet } = answers
// const isOnLine = useType === 'onLine'
const outputDir = path.resolve(process.cwd(), 'src/components/IconPicker/src/data')
fs.ensureDir(outputDir)
const genCollections = collections.filter((item) => [iconSet].includes(item.id))
const prefixSet: string[] = []
for (const info of genCollections) {
const data = await fs.readJSON(path.join(dir, 'json', `${info.id}.json`))
if (data) {
const { prefix } = data
const prefixName = `${ICON_PREFIX}${prefix}`
const icons = Object.keys(data.icons).map((item) => `${prefixName}:${item}`)
await fs.writeFileSync(
path.join('src/components/IconPicker/src/data', `icons.${prefix}.ts`),
`export default ${JSON.stringify({ name: info.name, prefix: prefixName, icons })}`
)
// ↓分类处理完成push类型名称
prefixSet.push(prefix)
}
}
console.log(
`${chalk.cyan(`[${pkg.name}]`)}` + ' - Icon generated successfully:' + `[${prefixSet}]`
)
})
}
generateIcon()

@ -0,0 +1,59 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useAppStore } from '@/store/modules/app'
import { ConfigGlobal } from '@/components/ConfigGlobal'
import { useDesign } from '@/hooks/web/useDesign'
import { ElNotification } from 'element-plus'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('app')
const appStore = useAppStore()
const currentSize = computed(() => appStore.getCurrentSize)
const greyMode = computed(() => appStore.getGreyMode)
appStore.initTheme()
ElNotification({
title: '提示',
type: 'warning',
duration: 0,
dangerouslyUseHTMLString: true,
message:
'<div><p><strong>遇事不决,请先查阅常见问题,说不定你能找到相关解答</strong></p><p><a href="https://element-plus-admin-doc.cn/guide/fqa.html" target="_blank">链接地址</a></p></div>'
})
</script>
<template>
<ConfigGlobal :size="currentSize">
<RouterView :class="greyMode ? `${prefixCls}-grey-mode` : ''" />
</ConfigGlobal>
</template>
<style lang="less">
@prefix-cls: ~'@{adminNamespace}-app';
.size {
width: 100%;
height: 100%;
}
html,
body {
padding: 0 !important;
margin: 0;
overflow: hidden;
.size;
#app {
.size;
}
}
.@{prefix-cls}-grey-mode {
filter: grayscale(100%);
}
</style>

@ -0,0 +1,11 @@
import request from '@/axios'
// 获取所有字典
export const getDictApi = () => {
return request.get({ url: '/mock/dict/list' })
}
// 模拟获取某个字典
export const getDictOneApi = async () => {
return request.get({ url: '/mock/dict/one' })
}

@ -0,0 +1,23 @@
import request from '@/axios'
import type {
AnalysisTotalTypes,
UserAccessSource,
WeeklyUserActivity,
MonthlySales
} from './types'
export const getCountApi = (): Promise<IResponse<AnalysisTotalTypes[]>> => {
return request.get({ url: '/mock/analysis/total' })
}
export const getUserAccessSourceApi = (): Promise<IResponse<UserAccessSource[]>> => {
return request.get({ url: '/mock/analysis/userAccessSource' })
}
export const getWeeklyUserActivityApi = (): Promise<IResponse<WeeklyUserActivity[]>> => {
return request.get({ url: '/mock/analysis/weeklyUserActivity' })
}
export const getMonthlySalesApi = (): Promise<IResponse<MonthlySales[]>> => {
return request.get({ url: '/mock/analysis/monthlySales' })
}

@ -0,0 +1,22 @@
export type AnalysisTotalTypes = {
users: number
messages: number
moneys: number
shoppings: number
}
export type UserAccessSource = {
value: number
name: string
}
export type WeeklyUserActivity = {
value: number
name: string
}
export type MonthlySales = {
name: string
estimate: number
actual: number
}

@ -0,0 +1,22 @@
import request from '@/axios'
import type { WorkplaceTotal, Project, Dynamic, Team, RadarData } from './types'
export const getCountApi = (): Promise<IResponse<WorkplaceTotal>> => {
return request.get({ url: '/mock/workplace/total' })
}
export const getProjectApi = (): Promise<IResponse<Project>> => {
return request.get({ url: '/mock/workplace/project' })
}
export const getDynamicApi = (): Promise<IResponse<Dynamic[]>> => {
return request.get({ url: '/mock/workplace/dynamic' })
}
export const getTeamApi = (): Promise<IResponse<Team[]>> => {
return request.get({ url: '/mock/workplace/team' })
}
export const getRadarApi = (): Promise<IResponse<RadarData[]>> => {
return request.get({ url: '/mock/workplace/radar' })
}

@ -0,0 +1,30 @@
export type WorkplaceTotal = {
project: number
access: number
todo: number
}
export type Project = {
name: string
icon: string
message: string
personal: string
time: Date | number | string
}
export type Dynamic = {
keys: string[]
time: Date | number | string
}
export type Team = {
name: string
icon: string
}
export type RadarData = {
personal: number
team: number
max: number
name: string
}

@ -0,0 +1,30 @@
import request from '@/axios'
import { DepartmentListResponse, DepartmentUserParams, DepartmentUserResponse } from './types'
export const getDepartmentApi = () => {
return request.get<DepartmentListResponse>({ url: '/mock/department/list' })
}
export const getUserByIdApi = (params: DepartmentUserParams) => {
return request.get<DepartmentUserResponse>({ url: '/mock/department/users', params })
}
export const deleteUserByIdApi = (ids: string[] | number[]) => {
return request.post({ url: '/mock/department/user/delete', data: { ids } })
}
export const saveUserApi = (data: any) => {
return request.post({ url: '/mock/department/user/save', data })
}
export const saveDepartmentApi = (data: any) => {
return request.post({ url: '/mock/department/save', data })
}
export const deleteDepartmentApi = (ids: string[] | number[]) => {
return request.post({ url: '/mock/department/delete', data: { ids } })
}
export const getDepartmentTableApi = (params: any) => {
return request.get({ url: '/mock/department/table/list', params })
}

@ -0,0 +1,32 @@
export interface DepartmentItem {
id: string
departmentName: string
children?: DepartmentItem[]
}
export interface DepartmentListResponse {
list: DepartmentItem[]
}
export interface DepartmentUserParams {
pageSize: number
pageIndex: number
id: string
username?: string
account?: string
}
export interface DepartmentUserItem {
id: string
username: string
account: string
email: string
createTime: string
role: string
department: DepartmentItem
}
export interface DepartmentUserResponse {
list: DepartmentUserItem[]
total: number
}

@ -0,0 +1,34 @@
import request from '@/axios'
import type { UserType } from './types'
interface RoleParams {
roleName: string
}
export const loginApi = (data: UserType): Promise<IResponse<UserType>> => {
return request.post({ url: '/mock/user/login', data })
}
export const loginOutApi = (): Promise<IResponse> => {
return request.get({ url: '/mock/user/loginOut' })
}
export const getUserListApi = ({ params }: AxiosConfig) => {
return request.get<{
code: string
data: {
list: UserType[]
total: number
}
}>({ url: '/mock/user/list', params })
}
export const getAdminRoleApi = (
params: RoleParams
): Promise<IResponse<AppCustomRouteRecordRaw[]>> => {
return request.get({ url: '/mock/role/list', params })
}
export const getTestRoleApi = (params: RoleParams): Promise<IResponse<string[]>> => {
return request.get({ url: '/mock/role/list2', params })
}

@ -0,0 +1,11 @@
export interface UserLoginType {
username: string
password: string
}
export interface UserType {
username: string
password: string
role: string
roleId: string
}

@ -0,0 +1,5 @@
import request from '@/axios'
export const getMenuListApi = () => {
return request.get({ url: '/mock/menu/list' })
}

@ -0,0 +1,38 @@
import request from '@/axios'
import { RequestResponse } from './types'
export const request1 = () => {
return request.get<IResponse<RequestResponse>>({
url: '/mock/request/1'
})
}
export const request2 = () => {
return request.get<IResponse<RequestResponse>>({
url: '/mock/request/2'
})
}
export const request3 = () => {
return request.get<IResponse<RequestResponse>>({
url: '/mock/request/3'
})
}
export const request4 = () => {
return request.get<IResponse<RequestResponse>>({
url: '/mock/request/4'
})
}
export const request5 = () => {
return request.get<IResponse<RequestResponse>>({
url: '/mock/request/5'
})
}
export const expired = () => {
return request.get<IResponse<RequestResponse>>({
url: '/mock/request/expired'
})
}

@ -0,0 +1,3 @@
export interface RequestResponse {
data: string
}

@ -0,0 +1,5 @@
import request from '@/axios'
export const getRoleListApi = () => {
return request.get({ url: '/mock/role/table' })
}

@ -0,0 +1,26 @@
import request from '@/axios'
import type { TableData } from './types'
export const getTableListApi = (params: any) => {
return request.get({ url: '/mock/example/list', params })
}
export const getCardTableListApi = (params: any) => {
return request.get({ url: '/mock/card/list', params })
}
export const getTreeTableListApi = (params: any) => {
return request.get({ url: '/mock/example/treeList', params })
}
export const saveTableApi = (data: Partial<TableData>): Promise<IResponse> => {
return request.post({ url: '/mock/example/save', data })
}
export const getTableDetApi = (id: string): Promise<IResponse<TableData>> => {
return request.get({ url: '/mock/example/detail', params: { id } })
}
export const delTableListApi = (ids: string[] | number[]): Promise<IResponse> => {
return request.post({ url: '/mock/example/delete', data: { ids } })
}

@ -0,0 +1,9 @@
export type TableData = {
id: string
author: string
title: string
content: string
importance: number
display_time: string
pageviews: number
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.147.062a13 13 0 014.94.945c1.55.63 2.907 1.526 4.069 2.688a13.148 13.148 0 012.761 4.069c.678 1.55 1.017 3.245 1.017 5.086v102.3c0 3.681-1.187 6.733-3.56 9.155-2.373 2.422-5.352 3.633-8.937 3.633H12.992c-3.875 0-7-1.26-9.373-3.779-2.373-2.518-3.56-5.667-3.56-9.445V12.704c0-3.39 1.163-6.345 3.488-8.863C5.872 1.32 8.972.062 12.847.062h102.3zM81.434 109.047c1.744 0 3.003-.412 3.778-1.235.775-.824 1.163-1.914 1.163-3.27 0-1.26-.388-2.325-1.163-3.197-.775-.872-2.034-1.307-3.778-1.307H72.57c.097-.194.145-.485.145-.872V27.09h9.01c1.743 0 2.954-.436 3.633-1.308.678-.872 1.017-1.938 1.017-3.197 0-1.26-.34-2.325-1.017-3.197-.679-.872-1.89-1.308-3.633-1.308H46.268c-1.743 0-2.954.436-3.632 1.308-.678.872-1.018 1.938-1.018 3.197 0 1.26.34 2.325 1.018 3.197.678.872 1.889 1.308 3.632 1.308h8.138v72.075c0 .193.024.339.073.436.048.096.072.242.072.436H46.56c-1.744 0-3.003.435-3.778 1.307-.775.872-1.163 1.938-1.163 3.197 0 1.356.388 2.446 1.163 3.27.775.823 2.034 1.235 3.778 1.235h34.875z"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="5760" height="3040"><image width="5760" height="3040" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAFoAAAAvgAQMAAAC1QKagAAAABGdBTUEAALGPC/xhBQAAACBjSFJN AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABlBMVEUsNEr///91v/yPAAAA AWJLR0QB/wIt3gAAAAd0SU1FB+YBBQYyN1c3BnEAAAhjSURBVHja7cExAQAAAMKg9U9tDB+gAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAACAtwFzzwABY3VrRQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMi0wMS0wNVQwNjo1 MDo1MyswMDowMCfNlVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjItMDEtMDVUMDY6NTA6NTQrMDA6 MDCTNxNoAAAAAElFTkSuQmCC"/></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 33 KiB

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 20.967v59.59c0 11.59 8.537 20.966 19.075 20.966h28.613l1 26.477L76.8 101.523h32.125c10.538 0 19.075-9.377 19.075-20.966v-59.59C128 9.377 119.463 0 108.925 0h-89.85C8.538 0 0 9.377 0 20.967zm82.325 33.1c0-5.524 4.013-9.935 9.037-9.935 5.026 0 9.038 4.41 9.038 9.934 0 5.524-4.025 9.934-9.038 9.934-5.024 0-9.037-4.41-9.037-9.934zm-27.613 0c0-5.524 4.013-9.935 9.038-9.935s9.037 4.41 9.037 9.934c0 5.524-4.025 9.934-9.037 9.934-5.025 0-9.038-4.41-9.038-9.934zm-27.1 0c0-5.524 4.013-9.935 9.038-9.935s9.038 4.41 9.038 9.934c0 5.524-4.026 9.934-9.05 9.934-5.013 0-9.025-4.41-9.025-9.934z"/></svg>

After

Width:  |  Height:  |  Size: 669 B

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M54.122 127.892v-28.68H7.513V87.274h46.609v-12.4H7.513v-12.86h38.003L.099 0h22.6l32.556 45.07c3.617 5.144 6.44 9.611 8.487 13.385 1.788-3.05 4.89-7.779 9.301-14.186L103.93 0h24.01L82.385 62.013h38.34v12.862h-46.41v12.4h46.41v11.937h-46.41v28.68H54.123z"/></svg>

After

Width:  |  Height:  |  Size: 335 B

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M95.648 118.762c0 5.035-3.563 9.121-7.979 9.121H7.98c-4.416 0-7.979-4.086-7.979-9.121C0 100.519 15.408 83.47 31.152 76.75c-9.099-6.43-15.216-17.863-15.216-30.987v-9.128c0-20.16 14.293-36.518 31.893-36.518s31.894 16.358 31.894 36.518v9.122c0 13.137-6.123 24.556-15.216 30.993 15.738 6.726 31.141 23.769 31.141 42.012z"/><path d="M106.032 118.252h15.867c3.376 0 6.101-3.125 6.101-6.972 0-13.957-11.787-26.984-23.819-32.123 6.955-4.919 11.638-13.66 11.638-23.704v-6.985c0-15.416-10.928-27.926-24.39-27.926-1.674 0-3.306.193-4.89.561 1.936 4.713 3.018 9.974 3.018 15.526v9.121c0 13.137-3.056 23.111-11.066 30.993 14.842 4.41 27.312 23.42 27.541 41.509z"/></svg>

After

Width:  |  Height:  |  Size: 731 B

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M42.913 101.36c1.642 0 3.198.332 4.667.996a12.28 12.28 0 013.89 2.772c1.123 1.184 1.987 2.582 2.592 4.193.605 1.612.908 3.318.908 5.118 0 1.8-.303 3.507-.908 5.118-.605 1.611-1.469 3.01-2.593 4.194a13.3 13.3 0 01-3.889 2.843 10.582 10.582 0 01-4.667 1.066c-1.729 0-3.306-.355-4.732-1.066a13.604 13.604 0 01-3.825-2.843c-1.123-1.185-1.988-2.583-2.593-4.194a14.437 14.437 0 01-.907-5.118c0-1.8.302-3.506.907-5.118.605-1.61 1.47-3.009 2.593-4.193a12.515 12.515 0 013.825-2.772c1.426-.664 3.003-.996 4.732-.996zm53.932.285c1.643 0 3.22.331 4.733.995a11.386 11.386 0 013.889 2.772c1.08 1.185 1.945 2.583 2.593 4.194.648 1.61.972 3.317.972 5.118 0 1.8-.324 3.506-.972 5.117-.648 1.611-1.513 3.01-2.593 4.194a12.253 12.253 0 01-3.89 2.843 11 11 0 01-4.732 1.066 10.58 10.58 0 01-4.667-1.066 12.478 12.478 0 01-3.824-2.843c-1.08-1.185-1.945-2.583-2.593-4.194a13.581 13.581 0 01-.973-5.117c0-1.801.325-3.507.973-5.118.648-1.611 1.512-3.01 2.593-4.194a11.559 11.559 0 013.824-2.772 11.212 11.212 0 014.667-.995zm21.781-80.747c2.42 0 4.3.355 5.64 1.066 1.34.71 2.29 1.587 2.852 2.63a6.427 6.427 0 01.778 3.34c-.044 1.185-.195 2.204-.454 3.057-.26.853-.8 2.606-1.62 5.26a589.268 589.268 0 01-2.788 8.743 1236.373 1236.373 0 00-3.047 9.453c-.994 3.128-1.75 5.592-2.269 7.393-1.123 3.79-2.55 6.42-4.278 7.89-1.728 1.469-3.846 2.203-6.352 2.203H39.023l1.945 12.795h65.342c4.148 0 6.223 1.943 6.223 5.828 0 1.896-.41 3.53-1.232 4.905-.821 1.374-2.442 2.061-4.862 2.061H38.505c-1.729 0-3.176-.426-4.343-1.28-1.167-.852-2.14-1.966-2.917-3.34a21.277 21.277 0 01-1.88-4.478 44.128 44.128 0 01-1.102-4.55c-.087-.568-.324-1.942-.713-4.122-.39-2.18-.865-4.904-1.426-8.174l-1.88-10.947c-.692-4.027-1.383-8.079-2.075-12.154-1.642-9.572-3.5-20.234-5.574-31.986H6.87c-1.296 0-2.377-.356-3.24-1.067a9.024 9.024 0 01-2.14-2.558 10.416 10.416 0 01-1.167-3.2C.108 8.53 0 7.488 0 6.54c0-1.896.583-3.46 1.75-4.69C2.917.615 4.494 0 6.482 0h13.095c1.728 0 3.111.284 4.148.853 1.037.569 1.858 1.28 2.463 2.132a8.548 8.548 0 011.297 2.701c.26.948.475 1.754.648 2.417.173.758.346 1.825.519 3.199.173 1.374.345 2.772.518 4.193.26 1.706.519 3.507.778 5.403h88.678z"/></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@ -0,0 +1,53 @@
import { AxiosResponse, InternalAxiosRequestConfig } from './types'
import { ElMessage } from 'element-plus'
import qs from 'qs'
import { SUCCESS_CODE, TRANSFORM_REQUEST_DATA } from '@/constants'
import { useUserStoreWithOut } from '@/store/modules/user'
import { objToFormData } from '@/utils'
const defaultRequestInterceptors = (config: InternalAxiosRequestConfig) => {
if (
config.method === 'post' &&
config.headers['Content-Type'] === 'application/x-www-form-urlencoded'
) {
config.data = qs.stringify(config.data)
} else if (
TRANSFORM_REQUEST_DATA &&
config.method === 'post' &&
config.headers['Content-Type'] === 'multipart/form-data' &&
!(config.data instanceof FormData)
) {
config.data = objToFormData(config.data)
}
if (config.method === 'get' && config.params) {
let url = config.url as string
url += '?'
const keys = Object.keys(config.params)
for (const key of keys) {
if (config.params[key] !== void 0 && config.params[key] !== null) {
url += `${key}=${encodeURIComponent(config.params[key])}&`
}
}
url = url.substring(0, url.length - 1)
config.params = {}
config.url = url
}
return config
}
const defaultResponseInterceptors = (response: AxiosResponse) => {
if (response?.config?.responseType === 'blob') {
// 如果是文件流,直接过
return response
} else if (response.data.code === SUCCESS_CODE) {
return response.data
} else {
ElMessage.error(response?.data?.message)
if (response?.data?.code === 401) {
const userStore = useUserStoreWithOut()
userStore.logout()
}
}
}
export { defaultResponseInterceptors, defaultRequestInterceptors }

@ -0,0 +1,42 @@
import service from './service'
import { CONTENT_TYPE } from '@/constants'
import { useUserStoreWithOut } from '@/store/modules/user'
const request = (option: AxiosConfig) => {
const { url, method, params, data, headers, responseType } = option
const userStore = useUserStoreWithOut()
return service.request({
url: url,
method,
params,
data: data,
responseType: responseType,
headers: {
'Content-Type': CONTENT_TYPE,
[userStore.getTokenKey ?? 'Authorization']: userStore.getToken ?? '',
...headers
}
})
}
export default {
get: <T = any>(option: AxiosConfig) => {
return request({ method: 'get', ...option }) as Promise<IResponse<T>>
},
post: <T = any>(option: AxiosConfig) => {
return request({ method: 'post', ...option }) as Promise<IResponse<T>>
},
delete: <T = any>(option: AxiosConfig) => {
return request({ method: 'delete', ...option }) as Promise<IResponse<T>>
},
put: <T = any>(option: AxiosConfig) => {
return request({ method: 'put', ...option }) as Promise<IResponse<T>>
},
cancelRequest: (url: string | string[]) => {
return service.cancelRequest(url)
},
cancelAllRequest: () => {
return service.cancelAllRequest()
}
}

@ -0,0 +1,77 @@
import axios, { AxiosError } from 'axios'
import { defaultRequestInterceptors, defaultResponseInterceptors } from './config'
import { AxiosInstance, InternalAxiosRequestConfig, RequestConfig, AxiosResponse } from './types'
import { ElMessage } from 'element-plus'
import { REQUEST_TIMEOUT } from '@/constants'
export const PATH_URL = import.meta.env.VITE_API_BASE_PATH
const abortControllerMap: Map<string, AbortController> = new Map()
const axiosInstance: AxiosInstance = axios.create({
timeout: REQUEST_TIMEOUT,
baseURL: PATH_URL
})
axiosInstance.interceptors.request.use((res: InternalAxiosRequestConfig) => {
const controller = new AbortController()
const url = res.url || ''
res.signal = controller.signal
abortControllerMap.set(
import.meta.env.VITE_USE_MOCK === 'true' ? url.replace('/mock', '') : url,
controller
)
return res
})
axiosInstance.interceptors.response.use(
(res: AxiosResponse) => {
const url = res.config.url || ''
abortControllerMap.delete(url)
// 这里不能做任何处理,否则后面的 interceptors 拿不到完整的上下文了
return res
},
(error: AxiosError) => {
console.log('err ' + error) // for debug
ElMessage.error(error.message)
return Promise.reject(error)
}
)
axiosInstance.interceptors.request.use(defaultRequestInterceptors)
axiosInstance.interceptors.response.use(defaultResponseInterceptors)
const service = {
request: (config: RequestConfig) => {
return new Promise((resolve, reject) => {
if (config.interceptors?.requestInterceptors) {
config = config.interceptors.requestInterceptors(config as any)
}
axiosInstance
.request(config)
.then((res) => {
resolve(res)
})
.catch((err: any) => {
reject(err)
})
})
},
cancelRequest: (url: string | string[]) => {
const urlList = Array.isArray(url) ? url : [url]
for (const _url of urlList) {
abortControllerMap.get(_url)?.abort()
abortControllerMap.delete(_url)
}
},
cancelAllRequest() {
for (const [_, controller] of abortControllerMap) {
controller.abort()
}
abortControllerMap.clear()
}
}
export default service

@ -0,0 +1,31 @@
import type {
InternalAxiosRequestConfig,
AxiosResponse,
AxiosRequestConfig,
AxiosInstance,
AxiosRequestHeaders,
AxiosError
} from 'axios'
interface RequestInterceptors<T> {
// 请求拦截
requestInterceptors?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig
requestInterceptorsCatch?: (err: any) => any
// 响应拦截
responseInterceptors?: (config: T) => T
responseInterceptorsCatch?: (err: any) => any
}
interface RequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
interceptors?: RequestInterceptors<T>
}
export {
AxiosResponse,
RequestInterceptors,
RequestConfig,
AxiosInstance,
InternalAxiosRequestConfig,
AxiosRequestHeaders,
AxiosError
}

@ -0,0 +1,4 @@
import Avatars from './src/Avatars.vue'
export type { AvatarItem } from './src/types'
export { Avatars }

@ -0,0 +1,79 @@
<script setup lang="ts">
import { ComponentSize, ElAvatar, ElTooltip } from 'element-plus'
import { PropType, computed } from 'vue'
import { AvatarItem } from './types'
import { useDesign } from '@/hooks/web/useDesign'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('avatars')
const props = defineProps({
size: {
type: [String, Number] as PropType<ComponentSize | number>,
default: ''
},
max: {
type: Number,
default: 5
},
data: {
type: Array as PropType<AvatarItem[]>,
default: () => []
},
showTooltip: {
type: Boolean,
default: true
}
})
const filterData = computed(() => props.data.slice(0, props.max))
</script>
<template>
<div :class="prefixCls" class="flex items-center">
<template v-for="item in filterData" :key="item.url">
<template v-if="showTooltip && item.name">
<ElTooltip :content="item.name" placement="top">
<ElAvatar
:size="size"
:src="item.url"
class="relative"
:style="{
zIndex: filterData.indexOf(item)
}"
/>
</ElTooltip>
</template>
<template v-else>
<ElAvatar
:size="size"
:src="item.url"
class="relative"
:style="{
zIndex: filterData.indexOf(item)
}"
/>
</template>
</template>
<ElAvatar
v-if="data.length > max"
:style="{
zIndex: data.length
}"
>
<span>+{{ data.length - max }}</span>
</ElAvatar>
</div>
</template>
<style scoped lang="less">
@prefix-cls: ~'@{adminNamespace}-avatars';
.@{prefix-cls} {
.@{elNamespace}-avatar + .@{elNamespace}-avatar {
margin-left: -15px;
}
}
</style>

@ -0,0 +1,4 @@
export interface AvatarItem {
url: string
name?: string
}

@ -0,0 +1,3 @@
import Backtop from './src/Backtop.vue'
export { Backtop }

@ -0,0 +1,15 @@
<script setup lang="ts">
import { ElBacktop } from 'element-plus'
import { useDesign } from '@/hooks/web/useDesign'
const { getPrefixCls, variables } = useDesign()
const prefixCls = getPrefixCls('backtop')
</script>
<template>
<ElBacktop
:class="prefixCls"
:target="`.${variables.namespace}-layout-content-scrollbar .${variables.elNamespace}-scrollbar__wrap`"
/>
</template>

@ -0,0 +1,3 @@
import Breadcrumb from './src/Breadcrumb.vue'
export { Breadcrumb }

@ -0,0 +1,126 @@
<script lang="tsx">
import { ElBreadcrumb, ElBreadcrumbItem } from 'element-plus'
import { ref, watch, computed, unref, defineComponent, TransitionGroup } from 'vue'
import { useRouter } from 'vue-router'
import { usePermissionStore } from '@/store/modules/permission'
import { filterBreadcrumb } from './helper'
import { filter, treeToList } from '@/utils/tree'
import type { RouteLocationNormalizedLoaded } from 'vue-router'
import { useI18n } from '@/hooks/web/useI18n'
import { Icon } from '@/components/Icon'
import { useAppStore } from '@/store/modules/app'
import { useDesign } from '@/hooks/web/useDesign'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('breadcrumb')
const appStore = useAppStore()
//
const breadcrumbIcon = computed(() => appStore.getBreadcrumbIcon)
export default defineComponent({
name: 'Breadcrumb',
setup() {
const { currentRoute } = useRouter()
const { t } = useI18n()
const levelList = ref<AppRouteRecordRaw[]>([])
const permissionStore = usePermissionStore()
const menuRouters = computed(() => {
const routers = permissionStore.getRouters
return filterBreadcrumb(routers)
})
const getBreadcrumb = () => {
const currentPath = currentRoute.value.matched.slice(-1)[0].path
levelList.value = filter<AppRouteRecordRaw>(unref(menuRouters), (node: AppRouteRecordRaw) => {
return node.path === currentPath
})
}
const renderBreadcrumb = () => {
const breadcrumbList = treeToList<AppRouteRecordRaw[]>(unref(levelList))
return breadcrumbList.map((v) => {
const disabled = !v.redirect || v.redirect === 'noredirect'
const meta = v.meta
return (
<ElBreadcrumbItem to={{ path: disabled ? '' : v.path }} key={v.name}>
{meta?.icon && breadcrumbIcon.value ? (
<>
<Icon icon={meta.icon} class="mr-[5px]"></Icon> {t(v?.meta?.title || '')}
</>
) : (
t(v?.meta?.title || '')
)}
</ElBreadcrumbItem>
)
})
}
watch(
() => currentRoute.value,
(route: RouteLocationNormalizedLoaded) => {
if (route.path.startsWith('/redirect/')) {
return
}
getBreadcrumb()
},
{
immediate: true
}
)
return () => (
<ElBreadcrumb separator="/" class={`${prefixCls} flex items-center h-full ml-[10px]`}>
<TransitionGroup appear enter-active-class="animate__animated animate__fadeInRight">
{renderBreadcrumb()}
</TransitionGroup>
</ElBreadcrumb>
)
}
})
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{elNamespace}-breadcrumb';
.@{prefix-cls} {
:deep(&__item) {
display: flex;
.@{prefix-cls}__inner {
display: flex;
align-items: center;
color: var(--top-header-text-color);
&:hover {
color: var(--el-color-primary);
}
}
}
:deep(&__item):not(:last-child) {
.@{prefix-cls}__inner {
color: var(--top-header-text-color);
&:hover {
color: var(--el-color-primary);
}
}
}
:deep(&__item):last-child {
.@{prefix-cls}__inner {
color: var(--el-text-color-placeholder);
&:hover {
color: var(--el-text-color-placeholder);
}
}
}
}
</style>

@ -0,0 +1,30 @@
import { pathResolve } from '@/utils/routerHelper'
export const filterBreadcrumb = (
routes: AppRouteRecordRaw[],
parentPath = ''
): AppRouteRecordRaw[] => {
const res: AppRouteRecordRaw[] = []
for (const route of routes) {
const meta = route?.meta
if (meta.hidden && !meta.canTo) {
continue
}
const data: AppRouteRecordRaw =
!meta.alwaysShow && route.children?.length === 1
? { ...route.children[0], path: pathResolve(route.path, route.children[0].path) }
: { ...route }
data.path = pathResolve(parentPath, data.path)
if (data.children) {
data.children = filterBreadcrumb(data.children, data.path)
}
if (data) {
res.push(data)
}
}
return res
}

@ -0,0 +1,3 @@
import BaseButton from './src/Button.vue'
export { BaseButton }

@ -0,0 +1,121 @@
<script setup lang="ts">
import { useDesign } from '@/hooks/web/useDesign'
import { ElButton, ComponentSize, ButtonType } from 'element-plus'
import { PropType, Component, computed, unref } from 'vue'
import { useAppStore } from '@/store/modules/app'
const appStore = useAppStore()
const getTheme = computed(() => appStore.getTheme)
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('button')
const props = defineProps({
size: {
type: String as PropType<ComponentSize>,
default: undefined
},
type: {
type: String as PropType<ButtonType>,
default: 'default'
},
disabled: {
type: Boolean,
default: false
},
plain: {
type: Boolean,
default: false
},
text: {
type: Boolean,
default: false
},
bg: {
type: Boolean,
default: false
},
link: {
type: Boolean,
default: false
},
round: {
type: Boolean,
default: false
},
circle: {
type: Boolean,
default: false
},
loading: {
type: Boolean,
default: false
},
loadingIcon: {
type: [String, Object] as PropType<string | Component>,
default: undefined
},
icon: {
type: [String, Object] as PropType<string | Component>,
default: undefined
},
autofocus: {
type: Boolean,
default: false
},
nativeType: {
type: String as PropType<'button' | 'submit' | 'reset'>,
default: 'button'
},
autoInsertSpace: {
type: Boolean,
default: false
},
color: {
type: String,
default: ''
},
darker: {
type: Boolean,
default: false
},
tag: {
type: [String, Object] as PropType<string | Component>,
default: 'button'
}
})
const emits = defineEmits(['click'])
const color = computed(() => {
const { type, link } = props
if (type === 'primary' && !link) {
return unref(getTheme).elColorPrimary
}
return ''
})
const style = computed(() => {
const { type, link } = props
if (type === 'primary' && !link) {
return '--el-button-text-color: #fff; --el-button-hover-text-color: #fff'
}
return ''
})
</script>
<template>
<ElButton
:class="`${prefixCls} color-#fff`"
v-bind="{ ...props }"
:color="color"
:style="style"
@click="() => emits('click')"
>
<slot></slot>
<slot name="icon"></slot>
<slot name="loading"></slot>
</ElButton>
</template>

@ -0,0 +1,3 @@
import CodeEditor from './src/CodeEditor.vue'
export { CodeEditor }

@ -0,0 +1,119 @@
<script setup lang="tsx">
import { useMonacoEditor } from '@/hooks/web/useMonacoEditor'
import { onMounted, computed, watch, ref } from 'vue'
import { ElSelect, ElOption, ElFormItem, ElForm } from 'element-plus'
import { languageOptions, themeOptions } from './config/config'
const props = withDefaults(
defineProps<{
width?: string | number
height?: string | number
languageSelector?: boolean
language?: string
themeSelector?: boolean
theme?: string
editorOption?: object
modelValue: string
}>(),
{
width: '100%',
height: '100%',
languageSelector: true,
language: 'javascript',
themeSelector: true,
theme: 'vs-dark',
editorOption: () => ({}),
modelValue: ''
}
)
const emits = defineEmits<{
(e: 'blur'): void
(e: 'update:modelValue', val: string): void
}>()
const monacoEditorStyle = computed(() => {
return {
width: typeof props.width === 'string' ? props.width : props.width + 'px',
height: typeof props.height === 'string' ? props.height : props.height + 'px'
}
})
const {
monacoEditorRef,
createEditor,
updateVal,
updateOptions,
getEditor,
changeLanguage,
changeTheme
} = useMonacoEditor(props.language)
onMounted(() => {
const monacoEditor = createEditor(props.editorOption)
updateMonacoVal(props.modelValue)
monacoEditor?.onDidChangeModelContent(() => {
emits('update:modelValue', monacoEditor!.getValue())
})
monacoEditor?.onDidBlurEditorText(() => {
emits('blur')
})
})
watch(
() => props.modelValue,
() => {
updateMonacoVal(props.modelValue)
}
)
const localLanguage = ref(props.language)
watch(localLanguage, (newLanguage) => {
changeLanguage(newLanguage)
})
const localTheme = ref(props.theme)
watch(localTheme, (newTheme) => {
changeTheme(newTheme)
})
function updateMonacoVal(val: string) {
if (val !== getEditor()?.getValue()) {
updateVal(val)
}
}
defineExpose({ updateOptions })
</script>
<template>
<ElForm :inline="true">
<ElFormItem v-if="languageSelector" label="language" class="w-30% mb-5px!">
<ElSelect
v-model="localLanguage"
placeholder="Please select language"
size="small"
filterable
>
<ElOption
v-for="item in languageOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</ElSelect>
</ElFormItem>
<ElFormItem v-if="themeSelector" label="theme" class="w-30% mb-5px!">
<ElSelect v-model="localTheme" placeholder="Please select language" size="small" filterable>
<ElOption
v-for="item in themeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</ElSelect>
</ElFormItem>
</ElForm>
<div ref="monacoEditorRef" :style="monacoEditorStyle"></div>
</template>

@ -0,0 +1,129 @@
export const languageOptions = [
{ label: 'plaintext', value: 'plaintext' },
{ label: 'abap', value: 'abap' },
{ label: 'apex', value: 'apex' },
{ label: 'azcli', value: 'azcli' },
{ label: 'bat', value: 'bat' },
{ label: 'bicep', value: 'bicep' },
{ label: 'cameligo', value: 'cameligo' },
{ label: 'clojure', value: 'clojure' },
{ label: 'coffeescript', value: 'coffeescript' },
{ label: 'c', value: 'c' },
{ label: 'cpp', value: 'cpp' },
{ label: 'csharp', value: 'csharp' },
{ label: 'csp', value: 'csp' },
{ label: 'css', value: 'css' },
{ label: 'cypher', value: 'cypher' },
{ label: 'dart', value: 'dart' },
{ label: 'dockerfile', value: 'dockerfile' },
{ label: 'ecl', value: 'ecl' },
{ label: 'elixir', value: 'elixir' },
{ label: 'flow9', value: 'flow9' },
{ label: 'fsharp', value: 'fsharp' },
{ label: 'freemarker2', value: 'freemarker2' },
{
label: 'freemarker2.tag-angle.interpolation-dollar',
value: 'freemarker2.tag-angle.interpolation-dollar'
},
{
label: 'freemarker2.tag-bracket.interpolation-dollar',
value: 'freemarker2.tag-bracket.interpolation-dollar'
},
{
label: 'freemarker2.tag-angle.interpolation-bracket',
value: 'freemarker2.tag-angle.interpolation-bracket'
},
{
label: 'freemarker2.tag-bracket.interpolation-bracket',
value: 'freemarker2.tag-bracket.interpolation-bracket'
},
{
label: 'freemarker2.tag-auto.interpolation-dollar',
value: 'freemarker2.tag-auto.interpolation-dollar'
},
{
label: 'freemarker2.tag-auto.interpolation-bracket',
value: 'freemarker2.tag-auto.interpolation-bracket'
},
{ label: 'go', value: 'go' },
{ label: 'graphql', value: 'graphql' },
{ label: 'handlebars', value: 'handlebars' },
{ label: 'hcl', value: 'hcl' },
{ label: 'html', value: 'html' },
{ label: 'ini', value: 'ini' },
{ label: 'java', value: 'java' },
{ label: 'javascript', value: 'javascript' },
{ label: 'julia', value: 'julia' },
{ label: 'kotlin', value: 'kotlin' },
{ label: 'less', value: 'less' },
{ label: 'lexon', value: 'lexon' },
{ label: 'lua', value: 'lua' },
{ label: 'liquid', value: 'liquid' },
{ label: 'm3', value: 'm3' },
{ label: 'markdown', value: 'markdown' },
{ label: 'mdx', value: 'mdx' },
{ label: 'mips', value: 'mips' },
{ label: 'msdax', value: 'msdax' },
{ label: 'mysql', value: 'mysql' },
{ label: 'objective-c', value: 'objective-c' },
{ label: 'pascal', value: 'pascal' },
{ label: 'pascaligo', value: 'pascaligo' },
{ label: 'perl', value: 'perl' },
{ label: 'pgsql', value: 'pgsql' },
{ label: 'php', value: 'php' },
{ label: 'pla', value: 'pla' },
{ label: 'postiats', value: 'postiats' },
{ label: 'powerquery', value: 'powerquery' },
{ label: 'powershell', value: 'powershell' },
{ label: 'proto', value: 'proto' },
{ label: 'pug', value: 'pug' },
{ label: 'python', value: 'python' },
{ label: 'qsharp', value: 'qsharp' },
{ label: 'r', value: 'r' },
{ label: 'razor', value: 'razor' },
{ label: 'redis', value: 'redis' },
{ label: 'redshift', value: 'redshift' },
{ label: 'restructuredtext', value: 'restructuredtext' },
{ label: 'ruby', value: 'ruby' },
{ label: 'rust', value: 'rust' },
{ label: 'sb', value: 'sb' },
{ label: 'scala', value: 'scala' },
{ label: 'scheme', value: 'scheme' },
{ label: 'scss', value: 'scss' },
{ label: 'shell', value: 'shell' },
{ label: 'sol', value: 'sol' },
{ label: 'aes', value: 'aes' },
{ label: 'sparql', value: 'sparql' },
{ label: 'sql', value: 'sql' },
{ label: 'st', value: 'st' },
{ label: 'swift', value: 'swift' },
{ label: 'systemverilog', value: 'systemverilog' },
{ label: 'verilog', value: 'verilog' },
{ label: 'tcl', value: 'tcl' },
{ label: 'twig', value: 'twig' },
{ label: 'typescript', value: 'typescript' },
{ label: 'vb', value: 'vb' },
{ label: 'wgsl', value: 'wgsl' },
{ label: 'xml', value: 'xml' },
{ label: 'yaml', value: 'yaml' },
{ label: 'json', value: 'json' }
]
export const themeOptions = [
{
label: 'vs',
value: 'vs'
},
{
label: 'vs-dark',
value: 'vs-dark'
},
{
label: 'hc-black',
value: 'hc-black'
},
{
label: 'hc-light',
value: 'hc-light'
}
]

@ -0,0 +1,3 @@
import Collapse from './src/Collapse.vue'
export { Collapse }

@ -0,0 +1,34 @@
<script setup lang="ts">
import { computed, unref } from 'vue'
import { useAppStore } from '@/store/modules/app'
import { propTypes } from '@/utils/propTypes'
import { useDesign } from '@/hooks/web/useDesign'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('collapse')
defineProps({
color: propTypes.string.def('')
})
const appStore = useAppStore()
const collapse = computed(() => appStore.getCollapse)
const toggleCollapse = () => {
const collapsed = unref(collapse)
appStore.setCollapse(!collapsed)
}
</script>
<template>
<div :class="prefixCls" @click="toggleCollapse">
<Icon
:size="18"
:icon="collapse ? 'vi-ant-design:menu-unfold-outlined' : 'vi-ant-design:menu-fold-outlined'"
:color="color"
class="cursor-pointer"
/>
</div>
</template>

@ -0,0 +1,5 @@
import ConfigGlobal from './src/ConfigGlobal.vue'
export type { ConfigGlobalTypes } from './src/types'
export { ConfigGlobal }

@ -0,0 +1,62 @@
<script setup lang="ts">
import { provide, computed, watch, onMounted } from 'vue'
import { propTypes } from '@/utils/propTypes'
import { ComponentSize, ElConfigProvider } from 'element-plus'
import { useLocaleStore } from '@/store/modules/locale'
import { useWindowSize } from '@vueuse/core'
import { useAppStore } from '@/store/modules/app'
import { setCssVar } from '@/utils'
import { useDesign } from '@/hooks/web/useDesign'
const { variables } = useDesign()
const appStore = useAppStore()
const props = defineProps({
size: propTypes.oneOf<ComponentSize>(['default', 'small', 'large']).def('default')
})
provide('configGlobal', props)
//
onMounted(() => {
appStore.setCssVarTheme()
})
const { width } = useWindowSize()
//
watch(
() => width.value,
(width: number) => {
if (width < 768) {
!appStore.getMobile ? appStore.setMobile(true) : undefined
setCssVar('--left-menu-min-width', '0')
appStore.setCollapse(true)
appStore.getLayout !== 'classic' ? appStore.setLayout('classic') : undefined
} else {
appStore.getMobile ? appStore.setMobile(false) : undefined
setCssVar('--left-menu-min-width', '64px')
}
},
{
immediate: true
}
)
//
const localeStore = useLocaleStore()
const currentLocale = computed(() => localeStore.currentLocale)
</script>
<template>
<ElConfigProvider
:namespace="variables.elNamespace"
:locale="currentLocale.elLocale"
:message="{ max: 1 }"
:size="size"
>
<slot></slot>
</ElConfigProvider>
</template>

@ -0,0 +1,5 @@
import { ComponentSize } from 'element-plus'
export interface ConfigGlobalTypes {
size?: ComponentSize
}

@ -0,0 +1,3 @@
import ContentDetailWrap from './src/ContentDetailWrap.vue'
export { ContentDetailWrap }

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

Loading…
Cancel
Save