Compare commits

...

91 Commits

Author SHA1 Message Date
xiaoxian521
c965e2cba2 release: update 4.5.0 2023-06-26 11:14:25 +08:00
xiaoxian521
b5996ed80b refactor: 重构图片裁剪ReCropper组件,添加更多实用功能 2023-06-25 18:33:46 +08:00
xiaoxian521
bf67e36731 fix: 修复RePureTableBar组件初始化时列设置勾选项未根据hide属性正确初始化 2023-06-24 00:52:54 +08:00
xiaoming
b8200125dc feat: 添加长按指令及使用示例,该长按指令支持自定义时长的持续回调 (#620) 2023-06-23 11:17:39 +08:00
xiaoxian521
5f71e0aad7 feat: 菜单搜索功能支持拼音搜索,比如搜图片裁剪,输入tptupian等对应拼音即可 2023-06-22 00:31:25 +08:00
xiaoxian521
5873caf596 chore(deps): update 2023-06-21 17:19:56 +08:00
xiaoxian521
5d87e9916f feat: 添加汉语拼音功能示例 2023-06-21 17:09:48 +08:00
xiaoxian521
861a93684d feat: 添加敏感词过滤功能示例 2023-06-21 14:58:34 +08:00
xiaoxian521
c354ba0bcd workflow: update 2023-06-19 17:06:06 +08:00
xiaoxian521
2a2a3ee478 fix: 修复升级到V4.4.0版本后,页面开启keepAlive缓存后第一次加载并未缓存页面的问题 2023-06-19 11:51:38 +08:00
xiaoxian521
2a8cd7affe chore(deps): update 2023-06-19 10:16:09 +08:00
xiaoxian521
0eade474eb chore(deps): update 2023-06-17 13:12:34 +08:00
xiaoxian521
cb9a8a0a05 perf: 兼容VITE_PUBLIC_PATHurlOSS场景,需将@pureadmin/theme升级至最新版 2023-06-15 13:03:58 +08:00
xiaoxian521
3e1bc7d677 perf: 将VITE_PUBLIC_PATH默认改为./兼容更多路径场景 2023-06-15 13:00:57 +08:00
xiaoxian521
d1db4b74f4 workflow: update 2023-06-15 00:50:50 +08:00
xiaoxian521
aad2100d96 chore: update 2023-06-14 11:08:47 +08:00
xiaoxian521
b7e799bfc7 release: update 4.4.0 2023-06-14 10:52:19 +08:00
xiaoxian521
79e91b7b13 chore(deps): update 2023-06-13 22:59:34 +08:00
xiaoxian521
58cafbc73f feat: 路由meta添加activePath可将某个菜单激活,主要用于通过queryparams传参的路由 2023-06-13 22:18:23 +08:00
xiaoming
5d86b714a4 perf: 页面切换性能优化 (#600)
* perf: 页面切换性能优化

* fix: 修复刷新页面时`router.beforeEach`调用两次的问题
2023-06-13 12:36:54 +08:00
xiaoxian521
aec2a35424 feat: 添加防抖、截流、复制自定义指令使用示例 2023-06-12 21:11:31 +08:00
xiaoxian521
3fd9b15698 feat: 添加文本复制自定义指令 2023-06-12 15:34:44 +08:00
xiaoxian521
d850496601 feat: 添加防抖节流指令并规范自定义指令用法错误时的提示 2023-06-12 13:53:54 +08:00
xiaoxian521
c06ce94746 fix: 对未解绑的公共事件,在页面销毁时解绑 2023-06-09 18:03:37 +08:00
xiaoxian521
ba2ec8aca2 refactor: 使用vueuseuseResizeObserver函数替换v-resize自定义指令,从测试后的表现来看,性能会更好 2023-06-09 17:27:05 +08:00
xiaoxian521
f971cd5b30 feat: pure-admin-table高级用法添加自适应内容区高度demo 2023-06-09 16:51:09 +08:00
xiaoxian521
39833ce917 perf: 系统管理中表格均改为自适应内容区高度,需将@pureadmin/table升级到最新版 2023-06-09 14:01:46 +08:00
Snlan
56368c1163 perf: 优化首页的GitHub信息展示
Co-authored-by: Snlan <pridewui@foxmail.com>
2023-06-08 22:21:24 +08:00
ChasonZheng
3471e4a7e2 feat: 函数式弹窗示例代码添加子组件propprimitive类型的demo (#587) 2023-06-07 21:59:02 +08:00
xiaoxian521
f613a79def fix: 修复搜索菜单功能的弹框遮罩未覆盖左侧菜单的问题 2023-06-07 11:00:47 +08:00
Snlan
04611d8b24 types: update 2023-06-06 19:09:20 +08:00
xiaoxian521
da6c2628d5 perf: notice消息提示组件空数据时添加el-empty组件 2023-06-06 15:00:55 +08:00
xiaoxian521
88a44f29d0 feat: 添加vscode-docker插件 2023-06-05 22:54:59 +08:00
xiaoxian521
315f78a825 fix: 修复国际化切换到英文模式刷新会回到中文模式 2023-06-05 19:29:11 +08:00
xiaoxian521
585adefbdd chore: eslint相关库以及typescript更新至最新版 2023-06-04 21:54:05 +08:00
xiaoxian521
abf076c9c6 chore: update 2023-06-04 12:41:38 +08:00
xiaoxian521
c3fd89a98d release: update 4.3.0 2023-06-04 12:11:51 +08:00
xiaoxian521
34379b9530 feat: 添加项目版本实时更新检测功能 2023-06-04 11:47:04 +08:00
xiaoxian521
7f02418839 docs: 添加docker支持 2023-05-31 18:18:27 +08:00
xiaoxian521
ee73c88d36 perf: pure-table表格高级用法添加保留已选中的CheckBox选项demo 2023-05-29 17:46:36 +08:00
xiaoxian521
87da9b881c perf: 优化标签页操作-路由传参模式用法 2023-05-29 11:30:53 +08:00
xiaoxian521
c4ddf840b4 chore(deps): update 2023-05-28 23:37:04 +08:00
xiaoxian521
8a34a4b1a3 style: 优化消息组件样式 2023-05-27 22:27:42 +08:00
xiaoxian521
1c89464d1a fix: 修复左侧菜单折叠后突然拉升造成左侧菜单整体不显示的问题 2023-05-27 21:55:29 +08:00
xiaoxian521
46db63e914 chore: update 2023-05-27 13:32:17 +08:00
xiaoxian521
0c12cba474 chore: update 2023-05-27 13:11:04 +08:00
xiaoxian521
b29500c4ed fix: 修复强制刷新页面getTopMenu()函数获取不到path报错的问题 2023-05-26 23:25:58 +08:00
xiaoxian521
727216acce chore(deps): update 2023-05-26 23:07:55 +08:00
xiaoxian521
d4ee45c3ce docs: update 2023-05-26 12:21:41 +08:00
xiaoxian521
ee51861ed7 chore(deps): update 2023-05-24 17:54:51 +08:00
xiaoxian521
82e994434a feat: wangeditor富文本添加多个富文本和自定义图片上传示例 2023-05-24 12:50:22 +08:00
xiaoxian521
68ca7549c3 perf: 优化dataThemeChange主题设置的初始化调用时机 2023-05-24 00:15:53 +08:00
xiaoxian521
e65faa5ef8 feat: 函数式弹框添加updateDialog更改弹框自身属性值方法 2023-05-23 21:56:24 +08:00
xiaoxian521
a4a691de3e fix: 修复动态路由重定向造成标签页出现重复内容 2023-05-23 20:43:16 +08:00
Netfan
b6ae64a89b perf: 从 .env.staging 文件移除多余的 NODE_ENV 配置 2023-05-23 14:39:36 +08:00
xiaoxian521
93ad811036 chore(deps): update 2023-05-22 23:50:13 +08:00
xiaoxian521
fe50a3618f docs: update 2023-05-22 16:40:13 +08:00
xiaoxian521
4e4c9b3848 chore: 更新vscode代码片段 2023-05-20 21:25:54 +08:00
xiaoxian521
5349c686d1 chore: update 2023-05-19 18:58:29 +08:00
Netfan
9b57e687a8 perf: 优化菜单搜索弹窗高度 (#559)
* perf: 优化菜单搜索弹窗高度

* chore: 优化键盘`up`、`down`事件
2023-05-19 17:47:04 +08:00
Netfan
1f21a2d364 feat: 优化PureTableBar组件,title可使用插槽 (#560)
* feat: 优化PureTableBar组件,title可使用插槽
2023-05-19 16:30:05 +08:00
xiaoxian521
79ff7c0462 chore: update 2023-05-19 10:49:37 +08:00
xiaoxian521
9efc9db2f0 chore(deps): update 2023-05-18 17:24:00 +08:00
xiaoxian521
ca6459f224 feat: 瀑布流组件添加无限滚动 2023-05-18 17:10:43 +08:00
xiaoxian521
d70b94111c style: update 2023-05-18 15:47:54 +08:00
xiaoming
af421a6489 feat: 完善角色管理页面 (#557) 2023-05-17 11:41:18 +08:00
valarchie
03fff4befb fix: 关闭列设置出现滚动条的问题以及错误字 (#556) 2023-05-16 21:37:59 +08:00
Netfan
d5fdc0449a perf: 优化图标选择器IconSelect组件 (#554)
* fix: 修复图标选择器未能正确初始化的问题(fix: #552)

* Update Select.vue

---------

Co-authored-by: xiaoming <1923740402@qq.com>
2023-05-16 14:29:16 +08:00
Netfan
730bae4bdb chore: update scripts-clean:cache in package.json
windows环境下缺少rm命令
2023-05-16 13:04:22 +08:00
guanrui Lu
0e632ac4ab chore: 优化菜单搜索 (#551)
* Update SearchModal.vue

* chore: 优化菜单搜索

---------

Co-authored-by: xiaoxian521 <1923740402@qq.com>
2023-05-16 12:53:06 +08:00
Zamir Zhang
6776e85641 fix: 修复获取验证码倒计时会有 1s 延时禁用的问题 2023-05-15 19:42:30 +08:00
xiaoxian521
7ffd7e2d7d chore: update 2023-05-15 19:13:27 +08:00
xiaoxian521
ee4fb9129e release: update 4.2.0 2023-05-15 15:03:57 +08:00
xiaoxian521
ecebb98ab6 feat: 新增分段控制器组件并适配暗黑模式 2023-05-15 14:41:35 +08:00
xiaoxian521
4e3d752fb6 feat: 新增组件-瀑布流demo 2023-05-14 16:38:29 +08:00
xiaoxian521
799a5140e3 feat: 静态路由支持配置数组格式 2023-05-14 14:20:06 +08:00
xiaoxian521
c19cb6004b chore(deps): update 2023-05-14 10:37:45 +08:00
许诺
0b8412c109 feat: 函数式弹框组件添加全屏、退出全屏操作按钮 (#546)
feat: 函数式弹框组件添加全屏、退出全屏操作按钮
2023-05-14 00:13:10 +08:00
xiaoxian521
5f3210f69e types: 添加 Exclusive 类型互斥语法糖 2023-05-13 19:39:16 +08:00
xiaoxian521
1aba27590f perf: 规范template模版中路由写法,不再使用$route$router,此写法vue-tsc编译不通过 2023-05-12 13:01:59 +08:00
xiaoxian521
a1b8b27a1c chore: update 2023-05-12 12:59:42 +08:00
xiaoxian521
6d141bfd2f release: update 4.1.0 2023-05-12 01:45:43 +08:00
xiaoxian521
86ec72f3c0 chore: 依赖更新到vue3.3+以及删除unplugin-vue-define-options插件 2023-05-11 20:26:58 +08:00
许诺
43ddf7aba8 feat: 优化 PureTableBar 组件,列展示添加拖拽功能 (#545)
* feat: add 表格工具列拖拽

* fix: 修复拖拽

* fix: 修复警告

* fix: 修复点击checkbox表格数据隐藏

* chore: update

---------

Co-authored-by: RealityBoy <1923740402@qq.com>
2023-05-11 19:58:49 +08:00
xiaoxian521
a71bf0befb perf: 完善系统管理-部门管理页面 2023-05-11 15:11:58 +08:00
xiaoxian521
47f951312e feat: 函数式弹框组件添加 beforeCancelbeforeSure回调 2023-05-10 17:42:43 +08:00
xiaoxian521
b2d06d2b3b feat: 封装 element-plusel-col 组件 2023-05-10 17:20:42 +08:00
xiaoxian521
7957dc2c18 feat: 函数式弹框组件添加结合Formdemo示例 2023-05-10 01:47:43 +08:00
HappyBoy
fd9c19dd00 fix: 修复在混合模式下刷新页签后,左侧菜单会消失闪一下的问题 (#543)
Co-authored-by: lixiangpeng <lixiangpeng@tbea.com>
2023-05-09 22:14:15 +08:00
xiaoxian521
b77586da07 perf: 优化首页布局 2023-05-09 22:03:57 +08:00
xiaoxian521
09cbc7ddc3 fix: 修复开启keepAlive后点击标签页的重新加载,页面缓存还存在的问题 2023-05-09 20:28:11 +08:00
160 changed files with 7071 additions and 3163 deletions

21
.dockerignore Normal file
View File

@@ -0,0 +1,21 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
.eslintcache
report.html
yarn.lock
npm-debug.log*
.pnpm-error.log*
.pnpm-debug.log
tests/**/coverage/
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
tsconfig.tsbuildinfo

View File

@@ -2,7 +2,7 @@
VITE_PORT = 8848
# 开发环境读取配置文件路径
VITE_PUBLIC_PATH = /
VITE_PUBLIC_PATH = ./
# 开发环境路由历史模式Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数"
VITE_ROUTER_HISTORY = "hash"

View File

@@ -1,5 +1,5 @@
# 线上环境平台打包路径
VITE_PUBLIC_PATH = /
VITE_PUBLIC_PATH = ./
# 线上环境路由历史模式Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数"
VITE_ROUTER_HISTORY = "hash"

View File

@@ -1,8 +1,8 @@
# 预发布也需要生产环境的行为
# https://cn.vitejs.dev/guide/env-and-mode.html#modes
NODE_ENV=production
# NODE_ENV = development
VITE_PUBLIC_PATH = /
VITE_PUBLIC_PATH = ./
# 预发布环境路由历史模式Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数"
VITE_ROUTER_HISTORY = "hash"

View File

@@ -1,38 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

38
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: "\U0001F41E Bug report"
description: Report an issue with vue-pure-admin
body:
- type: markdown
attributes:
value: |
感谢您花时间填写此错误报告 (Thanks for taking the time to fill out this bug report)
- type: textarea
id: bug-description
attributes:
label: 描述问题 (Describe the problem)
placeholder: 请描述您的问题 (Please describe your problem)
validations:
required: true
- type: textarea
id: reproduction-steps
attributes:
label: 如何复现该问题 (How to reproduce the problem)
placeholder: 请提供复现问题的具体操作步骤以便平台快速定位、高效地解决问题。当然如果问题的操作步骤较复杂您可以fork平台然后去改动代码复现问题这样更高效 (Please provide specific steps to reproduce the problem, so that the platform can quickly locate and solve the problem efficiently. Of course, if the operation steps of the problem are more complicated, you can fork the platform, and then modify the code to reproduce the problem, which is more efficient)
validations:
required: true
- type: textarea
id: system-info
attributes:
label: 操作系统和浏览器信息 (Operating system and browser information)
placeholder: 如果您遇到操作系统或浏览器兼容性问题,可选填此项 (Optional if you encounter operating system or browser compatibility issues)
validations:
required: false
- type: checkboxes
id: checkboxes
attributes:
label: 验证 (Verify)
description: 在提交问题之前,请确保您执行以下操作 (Before submitting an issue, please ensure you do the following)
options:
- label: 是否仔细阅读过 [文档](https://yiming_chang.gitee.io/pure-admin-doc/) (Have you read [documentation](https://yiming_chang.gitee.io/pure-admin-doc/) carefully)
required: true
- label: 检查是否存在相同或类似的问题 [issues](https://github.com/pure-admin/vue-pure-admin/issues) (Check for the same or similar [issues](https://github.com/pure-admin/vue-pure-admin/issues))
required: true

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1 @@
blank_issues_enabled: false

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -1,39 +0,0 @@
name: Build and Deploy
permissions:
contents: write
on:
push:
branches:
- master
jobs:
deploy:
concurrency: ci-${{ github.ref }}
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: "16"
registry-url: https://registry.npmjs.com/
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: latest
- name: Deploy 🔧
run: |
pnpm install --no-frozen-lockfile
sed -i "s#VITE_PUBLIC_PATH = /#VITE_PUBLIC_PATH = /vue-pure-admin/#g" $(pwd)/.env.production
pnpm build
cd dist
touch README.md .nojekyll
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@v4
with:
folder: dist
clean: true

View File

@@ -1,19 +1,4 @@
---
#################################
#################################
## Super Linter GitHub Actions ##
#################################
#################################
name: Lint Code Base
#
# Documentation:
# https://help.github.com/en/articles/workflow-syntax-for-github-actions
#
#############################
# Start the job on all push #
#############################
name: Lint Code
on:
push:
branches:
@@ -22,41 +7,41 @@ on:
branches:
- main
###############
# Set the Job #
###############
jobs:
build:
# Name the Job
name: Lint Code Base
# Set the agent to run on
name: Lint Code
runs-on: ubuntu-latest
##################
# Load all steps #
##################
steps:
##########################
# Checkout the code base #
##########################
- name: Checkout Code
uses: actions/checkout@v2
with:
# Full git history is needed to get a proper list of changed files within `super-linter`
fetch-depth: 0
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v2
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: "16"
registry-url: https://registry.npmjs.com/
node-version: 16
- name: Setup pnpm
uses: pnpm/action-setup@v2
- uses: pnpm/action-setup@v2
name: Install pnpm
id: pnpm-install
with:
version: latest
version: 7
run_install: false
- name: Build
- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Start Lint Code
run: |
pnpm install --no-frozen-lockfile
pnpm lint

View File

@@ -3,6 +3,7 @@
"christian-kohler.path-intellisense",
"vscode-icons-team.vscode-icons",
"davidanson.vscode-markdownlint",
"ms-azuretools.vscode-docker",
"stylelint.vscode-stylelint",
"bradlc.vscode-tailwindcss",
"dbaeumer.vscode-eslint",

View File

@@ -1,19 +1,19 @@
{
"Vue3.0快速生成模板": {
"scope": "vue",
"prefix": "Vue3.0",
"body": [
"<template>",
"\t<div>\n",
"\t</div>",
"\t<div>test</div>",
"</template>\n",
"<script lang='ts'>",
"export default {",
"\tsetup(){",
"\t\treturn{\n\n\t\t}",
"\t},",
"\tsetup() {",
"\t\treturn {}",
"\t}",
"}",
"</script>\n",
"<style scoped>\n",
"<style lang='scss' scoped>\n",
"</style>",
"$2"
],

View File

@@ -1,14 +1,14 @@
{
"Vue3.2+快速生成模板": {
"scope": "vue",
"prefix": "Vue3.2+",
"body": [
"<script setup lang='ts'>",
"</script>\n",
"<template>",
"\t<div>\n",
"\t</div>",
"\t<div>test</div>",
"</template>\n",
"<style scoped>\n",
"<style lang='scss' scoped>\n",
"</style>",
"$2"
],

20
.vscode/vue3.3.code-snippets vendored Normal file
View File

@@ -0,0 +1,20 @@
{
"Vue3.3+defineOptions快速生成模板": {
"scope": "vue",
"prefix": "Vue3.3+",
"body": [
"<script setup lang='ts'>",
"defineOptions({",
"\tname: ''",
"})",
"</script>\n",
"<template>",
"\t<div>test</div>",
"</template>\n",
"<style lang='scss' scoped>\n",
"</style>",
"$2"
],
"description": "Vue3.3+defineOptions快速生成模板"
}
}

View File

@@ -1,3 +1,114 @@
# 4.5.0 (2023-06-26)
### ✔️ refactor
- Refactor image crop `ReCropper` component, add more useful functions
### 🎫 Feat
- The menu search function supports pinyin search, such as searching for image cropping, input `tp` or `tupian` and other corresponding pinyin
- Add long press command and usage example, the long press command supports continuous callback of custom duration
- Add an example of sensitive word filtering function
- Add an example of Chinese Pinyin function
### 🐞 Bug fixes
- Fixed `V4.4.0` version, the problem that the page does not cache the page for the first time after the `keepAlive` cache is enabled
- Fixed the issue that the column setting tick option was not correctly initialized according to the `hide` property when the `RePureTableBar` component was initialized
### 🍏 Perf
- Change `VITE_PUBLIC_PATH` to `./` by default to be compatible with more path scenarios,
- Compatible with the `OSS` scene where `VITE_PUBLIC_PATH` is `url`, need to upgrade `@pureadin/theme` to the latest version
# 4.4.0 (2023-06-14)
### 🎫 Feat
- Route `meta` adds `activePath` attribute, which can activate a menu (mainly used for routes that pass parameters through `query` or `params`, when they are not displayed in the menu after configuring `showLink: false`, they will be There will be no menu highlighting, but you can get highlighting by setting `activePath` to specify the active menu, `activePath` is the `path` of the specified active menu [View details](https://github.com/pure-admin/vue-pure-admin/commit/58cafbc73ffa27253446ee93077e1e382519ce8a#commitcomment-117834411))
- Example of advanced usage of `pure-admin-table` to add adaptive content area height
- Add anti-shake, throttling and text copy instructions and standardize the prompts when custom instructions are used incorrectly and add usage examples
- Add `el-empty` component when the `notice` message prompts the component to have empty data
- Example code of functional popup window adding subcomponent `prop` as `primitive` type example
- Add `vscode-docker` plugin
### 🐞 Bug fixes
- Fix internationalization switch to English mode and refresh will return to Chinese mode
- Fixed the problem that the pop-up mask of the search menu function did not cover the left menu
### 🍏 Perf
- Page switching performance optimization, regardless of the network, the speed of page switching logic is almost `3-4` times faster than before [View optimization details](https://github.com/pure-admin/vue-pure-admin/pull/600#issuecomment-1586094078)
- Optimized tab page operation-routing parameter transfer mode usage
- All tables in the system management are changed to adaptive content area height, need to upgrade `@pureadmin/table` to the latest version
- Use the `useResizeObserver` function of `vueuse` to replace the `v-resize` custom directive, and the performance will be better from the performance after testing
- For unbound public events, unbind when the page is destroyed
# 4.3.0 (2023-06-04)
### 🎫 Feat
- Add `docker` support
- Add project version real-time update detection function
- Improve system management - role management page
- Waterfall component adds infinite scrolling
- Add `updateDialog` to the functional bullet box to change the property value of the bullet box itself
- `wangeditor` rich text add multiple rich text and custom image upload examples
- Advanced usage of `pure-table` table added keep checked `CheckBox` option example
- Added `title` slot to `RePureTableBar` component
### 🐞 Bug fixes
- Fixed the problem that the countdown to obtain the verification code will be disabled with a delay of `1s`
- Fixed the problem that the icon selector did not initialize the preview correctly
- Fixed dynamic routing redirection causing duplicate content on tabs
- Fix the problem that the `getTopMenu()` function cannot get `path` and report an error when the page is forced to refresh
- Fix the problem that the left menu does not display as a whole due to the sudden pull up after the left menu is folded
- Fixed `RePureTableBar` scrollbar issue in `windows` after turning off column settings
### 🍏 Perf
-Optimized tab page operation-routing parameter transfer mode usage
- Optimize menu search function and style
- Update `vscode` code snippets
- Optimize the initialization call timing of `dataThemeChange` theme setting
# 4.2.0 (2023-05-15)
### 🎫 Feat
- Added segment controller component and adapted to dark mode
- Static routing supports configuration array format
- Functional bullet box component adds full screen and exit full screen operation buttons
- New component - Waterfall `demo`
- Add `Exclusive` type mutual exclusion syntactic sugar
### 🍏 Perf
- Standardize the way of writing routes in `template` template, no longer use `$route` and `$router`, this way of writing `vue-tsc` fails to compile
# 4.1.0 (2023-05-12)
### 🎫 Feat
- Add a `demo` example combined with `Form` for the functional pop-up box component
- wrapper `el-col` component of `element-plus`
- Add `beforeCancel` and `beforeSure` callbacks to the functional popup component, which can suspend the closing of the popup
- Improve `System Management-Department Management` page
- Optimize `PureTableBar` component, add drag and drop function for column display
### 🐞 Bug fixes
- Fix the problem that the page cache still exists when you click the tab to reload after turning on `keepAlive`
- Fix the problem that the left menu will flicker after refreshing the tab in the mixed mode menu
### 🍏 Perf
- Optimize home page layout
- Dependency update to `vue3.3+` and remove `unplugin-vue-define-options` plugin
# 4.0.0 (2023-05-09)
[View 4.0.0 version optimization details](https://github.com/pure-admin/vue-pure-admin/issues/428#issuecomment-1422191158)

View File

@@ -1,3 +1,114 @@
# 4.5.0 (2023-06-26)
### ✔️ refactor
- Refactor image crop `ReCropper` component, add more useful functions
### 🎫 Feat
- The menu search function supports pinyin search, such as searching for image cropping, input `tp` or `tupian` and other corresponding pinyin
- Add long press command and usage example, the long press command supports continuous callback of custom duration
- Add an example of sensitive word filtering function
- Add an example of Chinese Pinyin function
### 🐞 Bug fixes
- Fixed `V4.4.0` version, the problem that the page does not cache the page for the first time after the `keepAlive` cache is enabled
- Fixed the issue that the column setting tick option was not correctly initialized according to the `hide` property when the `RePureTableBar` component was initialized
### 🍏 Perf
- Change `VITE_PUBLIC_PATH` to `./` by default to be compatible with more path scenarios,
- Compatible with the `OSS` scene where `VITE_PUBLIC_PATH` is `url`, need to upgrade `@pureadin/theme` to the latest version
# 4.4.0 (2023-06-14)
### 🎫 Feat
- Route `meta` adds `activePath` attribute, which can activate a menu (mainly used for routes that pass parameters through `query` or `params`, when they are not displayed in the menu after configuring `showLink: false`, they will be There will be no menu highlighting, but you can get highlighting by setting `activePath` to specify the active menu, `activePath` is the `path` of the specified active menu [View details](https://github.com/pure-admin/vue-pure-admin/commit/58cafbc73ffa27253446ee93077e1e382519ce8a#commitcomment-117834411))
- Example of advanced usage of `pure-admin-table` to add adaptive content area height
- Add anti-shake, throttling and text copy instructions and standardize the prompts when custom instructions are used incorrectly and add usage examples
- Add `el-empty` component when the `notice` message prompts the component to have empty data
- Example code of functional popup window adding subcomponent `prop` as `primitive` type example
- Add `vscode-docker` plugin
### 🐞 Bug fixes
- Fix internationalization switch to English mode and refresh will return to Chinese mode
- Fixed the problem that the pop-up mask of the search menu function did not cover the left menu
### 🍏 Perf
- Page switching performance optimization, regardless of the network, the speed of page switching logic is almost `3-4` times faster than before [View optimization details](https://github.com/pure-admin/vue-pure-admin/pull/600#issuecomment-1586094078)
- Optimized tab page operation-routing parameter transfer mode usage
- All tables in the system management are changed to adaptive content area height, need to upgrade `@pureadmin/table` to the latest version
- Use the `useResizeObserver` function of `vueuse` to replace the `v-resize` custom directive, and the performance will be better from the performance after testing
- For unbound public events, unbind when the page is destroyed
# 4.3.0 (2023-06-04)
### 🎫 Feat
- Add `docker` support
- Add project version real-time update detection function
- Improve system management - role management page
- Waterfall component adds infinite scrolling
- Add `updateDialog` to the functional bullet box to change the property value of the bullet box itself
- `wangeditor` rich text add multiple rich text and custom image upload examples
- Advanced usage of `pure-table` table added keep checked `CheckBox` option example
- Added `title` slot to `RePureTableBar` component
### 🐞 Bug fixes
- Fixed the problem that the countdown to obtain the verification code will be disabled with a delay of `1s`
- Fixed the problem that the icon selector did not initialize the preview correctly
- Fixed dynamic routing redirection causing duplicate content on tabs
- Fix the problem that the `getTopMenu()` function cannot get `path` and report an error when the page is forced to refresh
- Fix the problem that the left menu does not display as a whole due to the sudden pull up after the left menu is folded
- Fixed `RePureTableBar` scrollbar issue in `windows` after turning off column settings
### 🍏 Perf
-Optimized tab page operation-routing parameter transfer mode usage
- Optimize menu search function and style
- Update `vscode` code snippets
- Optimize the initialization call timing of `dataThemeChange` theme setting
# 4.2.0 (2023-05-15)
### 🎫 Feat
- Added segment controller component and adapted to dark mode
- Static routing supports configuration array format
- Functional bullet box component adds full screen and exit full screen operation buttons
- New component - Waterfall `demo`
- Add `Exclusive` type mutual exclusion syntactic sugar
### 🍏 Perf
- Standardize the way of writing routes in `template` template, no longer use `$route` and `$router`, this way of writing `vue-tsc` fails to compile
# 4.1.0 (2023-05-12)
### 🎫 Feat
- Add a `demo` example combined with `Form` for the functional pop-up box component
- wrapper `el-col` component of `element-plus`
- Add `beforeCancel` and `beforeSure` callbacks to the functional popup component, which can suspend the closing of the popup
- Improve `System Management-Department Management` page
- Optimize `PureTableBar` component, add drag and drop function for column display
### 🐞 Bug fixes
- Fix the problem that the page cache still exists when you click the tab to reload after turning on `keepAlive`
- Fix the problem that the left menu will flicker after refreshing the tab in the mixed mode menu
### 🍏 Perf
- Optimize home page layout
- Dependency update to `vue3.3+` and remove `unplugin-vue-define-options` plugin
# 4.0.0 (2023-05-09)
[View 4.0.0 version optimization details](https://github.com/pure-admin/vue-pure-admin/issues/428#issuecomment-1422191158)

View File

@@ -1,3 +1,113 @@
# 4.5.0 (2023-06-26)
### ✔️ refactor
- 重构图片裁剪 `ReCropper` 组件,添加更多实用功能
### 🎫 Feat
- 菜单搜索功能支持拼音搜索,比如搜图片裁剪,输入 `tp``tupian` 等对应拼音即可
- 添加长按指令及使用示例,该长按指令支持自定义时长的持续回调
- 添加敏感词过滤功能示例
- 添加汉语拼音功能示例
### 🐞 Bug fixes
- 修复 `V4.4.0` 版本,页面开启 `keepAlive` 缓存后第一次加载并未缓存页面的问题
- 修复 `RePureTableBar` 组件初始化时列设置勾选项未根据 `hide` 属性正确初始化
### 🍏 Perf
-`VITE_PUBLIC_PATH` 默认改为 `./` 兼容更多路径场景,
- 兼容 `VITE_PUBLIC_PATH``url``OSS` 场景,需将 `@pureadmin/theme` 升级至最新版
# 4.4.0 (2023-06-14)
### 🎫 Feat
- 路由 `meta` 添加 `activePath` 属性,可将某个菜单激活(主要用于通过 `query``params` 传参的路由,当它们通过配置 `showLink: false` 后不在菜单中显示,就不会有任何菜单高亮,而通过设置 `activePath` 指定激活菜单即可获得高亮,`activePath` 为指定激活菜单的 `path` [查看详情](https://github.com/pure-admin/vue-pure-admin/commit/58cafbc73ffa27253446ee93077e1e382519ce8a#commitcomment-117834411)
- `pure-admin-table` 高级用法添加自适应内容区高度示例
- 添加防抖、节流和文本复制指令并规范自定义指令用法错误时的提示以及添加使用示例
- `notice` 消息提示组件空数据时添加 `el-empty` 组件
- 函数式弹窗示例代码添加子组件 `prop``primitive` 类型的示例
- 添加 `vscode-docker` 插件
### 🐞 Bug fixes
- 修复国际化切换到英文模式刷新会回到中文模式
- 修复搜索菜单功能的弹框遮罩未覆盖左侧菜单的问题
### 🍏 Perf
- 页面切换性能优化,不考虑网络的情况下,页面切换逻辑的速度差不多比之前快 `3-4` 倍 [查看优化详情](https://github.com/pure-admin/vue-pure-admin/pull/600#issuecomment-1586094078)
- 优化标签页操作-路由传参模式用法
- 系统管理中表格均改为自适应内容区高度,需将 `@pureadmin/table` 升级到最新版
- 使用 `vueuse``useResizeObserver` 函数替换 `v-resize` 自定义指令,从测试后的表现来看性能会更好
- 对未解绑的公共事件,在页面销毁时解绑
# 4.3.0 (2023-06-04)
### 🎫 Feat
- 添加 `docker` 支持
- 添加项目版本实时更新检测功能
- 完善系统管理-角色管理页面
- 瀑布流组件添加无限滚动
- 函数式弹框添加 `updateDialog` 更改弹框自身属性值方法
- `wangeditor` 富文本添加多个富文本和自定义图片上传示例
- `pure-table` 表格高级用法添加保留已选中的 `CheckBox` 选项示例
- `RePureTableBar` 组件添加 `title` 插槽
### 🐞 Bug fixes
- 修复获取验证码倒计时会有 `1s` 延时禁用的问题
- 修复图标选择器未正确初始化预览问题
- 修复动态路由重定向造成标签页出现重复内容
- 修复强制刷新页面 `getTopMenu()` 函数获取不到 `path` 报错的问题
- 修复左侧菜单折叠后突然拉升造成左侧菜单整体不显示的问题
- 修复 `RePureTableBar` 关闭列设置后在 `windows` 出现滚动条的问题
### 🍏 Perf
- 优化标签页操作-路由传参模式用法
- 优化菜单搜索功能和样式
- 更新 `vscode` 代码片段
- 优化 `dataThemeChange` 主题设置的初始化调用时机
# 4.2.0 (2023-05-15)
### 🎫 Feat
- 新增分段控制器组件并适配暗黑模式
- 静态路由支持配置数组格式
- 函数式弹框组件添加全屏、退出全屏操作按钮
- 新增组件-瀑布流 `demo`
- 添加 `Exclusive` 类型互斥语法糖
### 🍏 Perf
- 规范 `template` 模版中路由写法,不再使用 `$route``$router`,此写法 `vue-tsc` 编译不通过
# 4.1.0 (2023-05-12)
### 🎫 Feat
- 函数式弹框组件添加结合 `Form``demo` 示例
- 封装 `element-plus``el-col` 组件
- 函数式弹框组件添加 `beforeCancel``beforeSure` 回调,可暂停弹框的关闭
- 完善 `系统管理-部门管理` 页面
- 优化 `PureTableBar` 组件,列展示添加拖拽功能
### 🐞 Bug fixes
- 修复开启 `keepAlive` 后点击标签页的重新加载,页面缓存还存在的问题
- 修复混合模式菜单下刷新页签后左侧菜单会闪烁一下的问题
### 🍏 Perf
- 优化首页布局
- 依赖更新到 `vue3.3+` 以及删除 `unplugin-vue-define-options` 插件
# 4.0.0 (2023-05-09)
[查看 4.0.0 版本优化细节](https://github.com/pure-admin/vue-pure-admin/issues/428#issuecomment-1422191158)

20
Dockerfile Normal file
View File

@@ -0,0 +1,20 @@
FROM node:16-alpine as build-stage
WORKDIR /app
RUN corepack enable
RUN corepack prepare pnpm@7.32.1 --activate
RUN npm config set registry https://registry.npmmirror.com
COPY .npmrc package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -22,10 +22,9 @@ The simplified version is based on the shelf extracted from [vue-pure-admin](htt
- [Click Watch Tutorial](https://www.bilibili.com/video/BV1kg411v7QT)
- [Click Watch UI Design](https://www.bilibili.com/video/BV17g411T7rq)
## Docs (support `PWA` fast, offline access)
## Docs
- [Click me to view the domestic documentation site](https://yiming_chang.gitee.io/pure-admin-doc)
- [Click me to view foreign document site](https://pure-admin.github.io/pure-admin-doc)
- [documentation site](https://yiming_chang.gitee.io/pure-admin-doc)
## Tauri
@@ -37,8 +36,7 @@ The simplified version is based on the shelf extracted from [vue-pure-admin](htt
## Preview
- [Click me to view the domestic preview station](https://yiming_chang.gitee.io/vue-pure-admin)
- [Click me to view foreign preview site](https://pure-admin.github.io/vue-pure-admin)
- [preview station](https://yiming_chang.gitee.io/vue-pure-admin)
- PC
<p align="center">
@@ -92,6 +90,28 @@ pnpm serve
pnpm build
```
## Docker support
1. Customize the image named `vue-pure-admin` (please note that there is a dot `.` at the end of the command below, indicating that the `Dockerfile` file in the current path is used, and the path can be specified according to the actual situation)
```bash
docker build -t vue-pure-admin .
```
2. Port mapping and start the `docker` container (`8080:80`: indicates that the `80` port is used in the container, and the port is forwarded to the `8080` port of the host; `pure-admin`: indicates a custom container name; `vue-pure-admin`: indicates the custom image name)
```bash
docker run -dp 8080:80 --name pure-admin vue-pure-admin
```
After operating the above two commands, open `http://localhost:8080` in the browser to preview
Of course, you can also operate the `docker` project through the [Docker Desktop](https://www.docker.com/products/docker-desktop/) visual interface, as shown below
<p align="center">
<img alt="docker" width="100%" src="https://yiming_chang.gitee.io/pure-admin-doc/img/docker/1.jpg">
</p>
## Change Log
[CHANGELOG](./CHANGELOG.en_US.md)
@@ -140,34 +160,12 @@ Support modern browsers, not IE
[xiaoxian521](https://github.com/xiaoxian521)、[Ten-K](https://github.com/Ten-K)
## Donate
If you think this project is helpful to you, you can help the author buy a glass of juice 🍹 Show your support
<img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f69bf13c5b854ed5b699807cafa0e3ce~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?" width="150px" height="150px" />
## License
In principle, no fees and copyrights are charged, and you can use it with confidence, but if you need secondary open source, please contact the author for permission!
In principle, no fees and copyrights are charged, and it is commercially available, but if you need secondary open source (such as using this platform for secondary development and open source, the front-end code must be open source and free), please contact the author for permission! (Free, just take a record)
[MIT © 2020-present, pure-admin](./LICENSE)
## Backers
Thank you very much for your support, I believe the project will get better and better :heart:
| xueyuheng | taolei1990 | hang-kim | madwolfcrazy | limuen | BenLakes |
| :--------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: |
| <a href="https://github.com/xueyuheng"><img src="https://avatars.githubusercontent.com/u/48202935?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/taolei1990"><img src="https://avatars.githubusercontent.com/u/23173640?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/hang-kim"><img src="https://avatars.githubusercontent.com/u/52914259?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/madwolfcrazy"><img src="https://avatars.githubusercontent.com/u/223671?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/limuen"><img src="https://avatars.githubusercontent.com/u/31790606?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/BenLakes"><img src="https://avatars.githubusercontent.com/u/15206046?v=4" width="60px" height="60px" /></a> |
| mollerzhu | TLovers | cnyyk | | | |
| <a href="https://github.com/mollerzhu"><img src="https://avatars.githubusercontent.com/u/49627902?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/TLovers"><img src="https://avatars.githubusercontent.com/u/26561694?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/cnyyk"><img src="https://avatars.githubusercontent.com/u/275233?v=4" width="60px" height="60px" /></a> | | | |
## Contributors
Thanks to all the people who contribute :heart:
<a href="https://github.com/pure-admin/vue-pure-admin/graphs/contributors"><img src="https://contrib.rocks/image?repo=pure-admin/vue-pure-admin" /></a>
## `Star`
Many thanks to the kind individuals who leave a star. Your support is much appreciated :heart:

View File

@@ -22,10 +22,9 @@
- [点我查看快速开发教程](https://www.bilibili.com/video/BV1kg411v7QT)
- [点我查看 UI 设计](https://www.bilibili.com/video/BV17g411T7rq)
## 配套保姆级文档(支持 `PWA` 快速、离线访问)
## 配套保姆级文档
- [点我查看国内文档](https://yiming_chang.gitee.io/pure-admin-doc)
- [点我查看国外文档站](https://pure-admin.github.io/pure-admin-doc)
- [查看文档](https://yiming_chang.gitee.io/pure-admin-doc)
## `Tauri` 版
@@ -37,8 +36,7 @@
## 预览
- [点我查看国内预览](https://yiming_chang.gitee.io/vue-pure-admin)
- [点我查看国外预览站](https://pure-admin.github.io/vue-pure-admin)
- [查看预览](https://yiming_chang.gitee.io/vue-pure-admin)
- PC 端
<p align="center">
@@ -92,6 +90,28 @@ pnpm serve
pnpm build
```
## Docker 支持
1. 自定义镜像名为 `vue-pure-admin` 的镜像(请注意下面命令末尾有一个点 `.` 表示使用当前路径下的 `Dockerfile` 文件,可根据实际情况指定路径)
```bash
docker build -t vue-pure-admin .
```
2. 端口映射并启动 `docker` 容器(`8080:80`:表示在容器中使用 `80` 端口,并将该端口转发到主机的 `8080` 端口;`pure-admin`:表示自定义容器名;`vue-pure-admin`:表示自定义镜像名)
```bash
docker run -dp 8080:80 --name pure-admin vue-pure-admin
```
操作完上面两个命令后,在浏览器打开 `http://localhost:8080` 即可预览
当然也可以通过 [Docker Desktop](https://www.docker.com/products/docker-desktop/) 可视化界面去操作 `docker` 项目,如下图
<p align="center">
<img alt="docker" width="100%" src="https://yiming_chang.gitee.io/pure-admin-doc/img/docker/1.jpg">
</p>
## 更新日志
[CHANGELOG](./CHANGELOG.zh_CN.md)
@@ -140,38 +160,12 @@ pnpm build
[xiaoxian521](https://github.com/xiaoxian521)、[Ten-K](https://github.com/Ten-K)
## 支持
如果您觉得这个项目对您有帮助,可以帮作者买一杯果汁 🍹 表示支持
<img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f69bf13c5b854ed5b699807cafa0e3ce~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?" width="150px" height="150px" />
## `QQ` 交流群
[点击去加入](https://yiming_chang.gitee.io/pure-admin-doc/pages/support/#qq-%E4%BA%A4%E6%B5%81%E7%BE%A4)
## 许可证
原则上不收取任何费用及版权,可以放心使用,不过如需二次开源(比如用此平台二次开发并开源)请联系作者获取许可!
原则上不收取任何费用及版权,可用,不过如需二次开源(比如用此平台二次开发并开源,要求前端代码必须开源免费)请联系作者获取许可!(免费,走个记录而已)
[MIT © 2020-present, pure-admin](./LICENSE)
## 支持者
非常感谢您们的支持,相信项目会越来越好 :heart:
| xueyuheng | taolei1990 | hang-kim | madwolfcrazy | limuen | BenLakes |
| :--------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: |
| <a href="https://github.com/xueyuheng"><img src="https://avatars.githubusercontent.com/u/48202935?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/taolei1990"><img src="https://avatars.githubusercontent.com/u/23173640?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/hang-kim"><img src="https://avatars.githubusercontent.com/u/52914259?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/madwolfcrazy"><img src="https://avatars.githubusercontent.com/u/223671?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/limuen"><img src="https://avatars.githubusercontent.com/u/31790606?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/BenLakes"><img src="https://avatars.githubusercontent.com/u/15206046?v=4" width="60px" height="60px" /></a> |
| mollerzhu | TLovers | cnyyk | | | |
| <a href="https://github.com/mollerzhu"><img src="https://avatars.githubusercontent.com/u/49627902?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/TLovers"><img src="https://avatars.githubusercontent.com/u/26561694?v=4" width="60px" height="60px" /></a> | <a href="https://github.com/cnyyk"><img src="https://avatars.githubusercontent.com/u/275233?v=4" width="60px" height="60px" /></a> | | | |
## 贡献者
感谢所有做出贡献的人 :heart:
<a href="https://github.com/pure-admin/vue-pure-admin/graphs/contributors"><img src="https://contrib.rocks/image?repo=pure-admin/vue-pure-admin" /></a>
## `Star`
非常感谢留下星星的好心人,感谢您的支持 :heart:

View File

@@ -15,10 +15,13 @@ const include = [
"intro.js",
"vue-i18n",
"js-cookie",
"vue-tippy",
"cropperjs",
"jsbarcode",
"pinyin-pro",
"sortablejs",
"swiper/vue",
"mint-filter",
"md-editor-v3",
"@vueuse/core",
"vue3-danmaku",
@@ -33,9 +36,9 @@ const include = [
"@howdyjs/mouse-menu",
"@logicflow/extension",
"vue-virtual-scroller",
"element-resize-detector",
"@amap/amap-jsapi-loader",
"el-table-infinite-scroll",
"vue-waterfall-plugin-next",
"@wangeditor/editor-for-vue",
"vuedraggable/src/vuedraggable"
];

View File

@@ -10,7 +10,6 @@ import { visualizer } from "rollup-plugin-visualizer";
import removeConsole from "vite-plugin-remove-console";
import themePreprocessorPlugin from "@pureadmin/theme";
import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite";
import DefineOptions from "unplugin-vue-define-options/vite";
import { genScssMultipleScopeVars } from "../src/layout/theme";
export function getPluginsList(
@@ -31,7 +30,6 @@ export function getPluginsList(
vueJsx(),
VITE_CDN ? cdn : null,
configCompressPlugin(VITE_COMPRESSION),
DefineOptions(),
// 线上环境删除console
removeConsole({ external: ["src/assets/iconfont/iconfont.js"] }),
viteBuildInfo(),

View File

@@ -39,6 +39,8 @@ menus:
hsdialog: Dialog Components
hsmessage: Message Tips Components
hsvideo: Video Components
hssegmented: Segmented Components
hswaterfall: Waterfall Components
hsmap: Map Components
hsdraggable: Draggable Components
hssplitPane: Split Pane
@@ -66,6 +68,7 @@ menus:
hsguide: Guide
hsAble: Able
hsMenuTree: Menu Tree
hsOptimize: Debounce、Throttle、Copy、Longpress Directives
hsWatermark: Water Mark
hsPrint: Print
hsDownload: Download
@@ -97,6 +100,8 @@ menus:
hsPdf: PDF Preview
hsExecl: Export Excel
hsInfiniteScroll: Table Infinite Scroll
hsSensitive: Sensitive Filter
hsPinyin: PinYin
hsdanmaku: Danmaku Components
hsPureTableBase: Base Usage
hsPureTableHigh: High Usage

View File

@@ -39,6 +39,8 @@ menus:
hsdialog: 函数式弹框组件
hsmessage: 消息提示组件
hsvideo: 视频组件
hssegmented: 分段控制器组件
hswaterfall: 瀑布流无限滚动组件
hsmap: 地图组件
hsdraggable: 拖拽组件
hssplitPane: 切割面板
@@ -66,6 +68,7 @@ menus:
hsguide: 引导页
hsAble: 功能
hsMenuTree: 菜单树结构
hsOptimize: 防抖、截流、复制、长按指令
hsWatermark: 水印
hsPrint: 打印
hsDownload: 下载
@@ -97,9 +100,11 @@ menus:
hsPdf: PDF预览
hsExecl: 导出Excel
hsInfiniteScroll: 表格无限滚动
hsSensitive: 敏感词过滤
hsPinyin: 汉语拼音
hsdanmaku: 弹幕组件
hsPureTableBase: 基础用法23个示例
hsPureTableHigh: 高级用法10个示例)
hsPureTableHigh: 高级用法11个示例)
hsTree: 大数据树业务组件
hsMenuoverflow: 目录超出显示 Tooltip 文字提示
hsChildMenuoverflow: 菜单超出显示 Tooltip 文字提示

View File

@@ -179,6 +179,7 @@ const tabsRouter = {
meta: {
// 不在menu菜单中显示
showLink: false,
activePath: "/tabs/index",
roles: ["admin", "common"]
}
},
@@ -190,6 +191,7 @@ const tabsRouter = {
meta: {
// 不在menu菜单中显示
showLink: false,
activePath: "/tabs/index",
roles: ["admin", "common"]
}
}

View File

@@ -451,5 +451,224 @@ export default [
}
};
}
},
{
// https://api.github.com/repos/pure-admin/vue-pure-admin/releases?per_page=100
url: "/releases",
method: "get",
response: () => {
return {
success: true,
data: {
list: [
{
created_at: "2023-06-14T02:52:19Z",
published_at: "2023-06-14T02:54:41Z",
body: "# 4.4.0 (2023-06-14)\r\n\r\n### 🎫 Feat\r\n\r\n- 路由 `meta` 添加 `activePath` 属性,可将某个菜单激活(主要用于通过 `query` 或 `params` 传参的路由,当它们通过配置 `showLink: false` 后不在菜单中显示,就不会有任何菜单高亮,而通过设置 `activePath` 指定激活菜单即可获得高亮,`activePath` 为指定激活菜单的 `path` [查看详情](https://github.com/pure-admin/vue-pure-admin/commit/58cafbc73ffa27253446ee93077e1e382519ce8a#commitcomment-117834411)\r\n- `pure-admin-table` 高级用法添加自适应内容区高度示例\r\n- 添加防抖、节流和文本复制指令并规范自定义指令用法错误时的提示以及添加使用示例\r\n- `notice` 消息提示组件空数据时添加 `el-empty` 组件\r\n- 函数式弹窗示例代码添加子组件 `prop` 为 `primitive` 类型的示例\r\n- 添加 `vscode-docker` 插件\r\n\r\n### 🐞 Bug fixes\r\n\r\n- 修复国际化切换到英文模式刷新会回到中文模式\r\n- 修复搜索菜单功能的弹框遮罩未覆盖左侧菜单的问题\r\n\r\n### 🍏 Perf\r\n\r\n- 页面切换性能优化,不考虑网络的情况下,页面切换逻辑的速度差不多比之前快 `3-4` 倍 [查看优化详情](https://github.com/pure-admin/vue-pure-admin/pull/600#issuecomment-1586094078)\r\n- 优化标签页操作-路由传参模式用法\r\n- 系统管理中表格均改为自适应内容区高度,需将 `@pureadmin/table` 升级到最新版\r\n- 使用 `vueuse` 的 `useResizeObserver` 函数替换 `v-resize` 自定义指令,从测试后的表现来看性能会更好\r\n- 对未解绑的公共事件,在页面销毁时解绑"
},
{
created_at: "2023-06-04T04:11:51Z",
published_at: "2023-06-04T04:13:24Z",
body: "# 4.3.0 (2023-06-04)\r\n\r\n### 🎫 Feat\r\n\r\n- 添加 `docker` 支持\r\n- 添加项目版本实时更新检测功能\r\n- 完善系统管理-角色管理页面\r\n- 瀑布流组件添加无限滚动\r\n- 函数式弹框添加 `updateDialog` 更改弹框自身属性值方法\r\n- `wangeditor` 富文本添加多个富文本和自定义图片上传示例\r\n- `pure-table` 表格高级用法添加保留已选中的 `CheckBox` 选项示例\r\n- `RePureTableBar` 组件添加 `title` 插槽\r\n\r\n### 🐞 Bug fixes\r\n\r\n- 修复获取验证码倒计时会有 `1s` 延时禁用的问题\r\n- 修复图标选择器未正确初始化预览问题\r\n- 修复动态路由重定向造成标签页出现重复内容\r\n- 修复强制刷新页面 `getTopMenu()` 函数获取不到 `path` 报错的问题\r\n- 修复左侧菜单折叠后突然拉升造成左侧菜单整体不显示的问题\r\n- 修复 `RePureTableBar` 关闭列设置后在 `windows` 出现滚动条的问题\r\n\r\n### 🍏 Perf\r\n\r\n- 优化标签页操作-路由传参模式用法\r\n- 优化菜单搜索功能和样式\r\n- 更新 `vscode` 代码片段\r\n- 优化 `dataThemeChange` 主题设置的初始化调用时机"
},
{
created_at: "2023-05-15T07:03:57Z",
published_at: "2023-05-15T07:04:54Z",
body: "# 4.2.0 (2023-05-15)\r\n\r\n### 🎫 Feat\r\n\r\n- 新增分段控制器组件并适配暗黑模式\r\n- 静态路由支持配置数组格式\r\n- 函数式弹框组件添加全屏、退出全屏操作按钮\r\n- 新增组件-瀑布流 `demo`\r\n- 添加 `Exclusive` 类型互斥语法糖\r\n\r\n### 🍏 Perf\r\n\r\n- 规范 `template` 模版中路由写法,不再使用 `$route` 和 `$router`,此写法 `vue-tsc` 编译不通过"
},
{
created_at: "2023-05-11T17:45:43Z",
published_at: "2023-05-11T17:47:10Z",
body: "# 4.1.0 (2023-05-12)\r\n\r\n### 🎫 Feat\r\n\r\n- 函数式弹框组件添加结合 `Form` 的 `demo` 示例\r\n- 封装 `element-plus` 的 `el-col` 组件\r\n- 函数式弹框组件添加 `beforeCancel` 和 `beforeSure` 回调,可暂停弹框的关闭\r\n- 完善 `系统管理-部门管理` 页面\r\n- 优化 `PureTableBar` 组件,列展示添加拖拽功能\r\n\r\n### 🐞 Bug fixes\r\n\r\n- 修复开启 `keepAlive` 后点击标签页的重新加载,页面缓存还存在的问题\r\n- 修复混合模式菜单下刷新页签后左侧菜单会闪烁一下的问题\r\n\r\n### 🍏 Perf\r\n\r\n- 优化首页布局\r\n- 依赖更新到 `vue3.3+` 以及删除 `unplugin-vue-define-options` 插件"
},
{
created_at: "2023-05-09T08:11:28Z",
published_at: "2023-05-09T08:14:55Z",
body: '# 4.0.0 (2023-05-09)\r\n\r\n[查看 4.0.0 版本优化细节](https://github.com/pure-admin/vue-pure-admin/issues/428#issuecomment-1422191158)\r\n\r\n### ✔️ refactor\r\n\r\n- 采用 `css` 伪类 `before` 写法重构菜单的激活背景,类似于 [ant.design-menu](https://ant.design/components/menu-cn#components-menu-demo-inline-collapsed)\r\n\r\n### 🎫 Feat\r\n\r\n- 优化菜单名称右侧的额外图标,使其支持更多图标渲染模式\r\n- 可配置首页菜单显示与隐藏\r\n- 将本地响应式存储的命名空间提升到全局配置中\r\n- 新增函数式弹框组件以及 `demo` 示例,使用更便捷\r\n- `PureTableBar` 组件添加列展示功能\r\n\r\n### 🐞 Bug fixes\r\n\r\n- 修复当菜单折叠或展开时首页 `echarts` 图表未自适应容器\r\n- 修复当只有一个子菜单时,搜索功能搜索不到该子菜单问题\r\n- 修复全局配置 `Theme` 为 `light` 清空缓存重新登录主题配置不生效的问题\r\n- 修复菜单搜索功能弹框打开后搜索框未自动聚集的问题\r\n- 修复按 `ESC` 退出全屏后,工具栏按钮文案展示问题\r\n- 修复移动端通知栏 `tooltip` 点击穿透问题\r\n- 修复当左侧菜单收起后,切换到 `horizontal` 导航模式时文字不展示的问题\r\n- 修复导航 `tab` 关闭其他标签页无法重置状态问题\r\n- 修复 `getHistoryMode` 函数中环境变量未初始化带来的页面热更新报错\r\n- 修复导航 `tab` 过多导致关闭左侧标签页无法正常显示\r\n- 修复点击内容区全屏报错问题\r\n- 修复混合导航下打开 `showLink:false` 页面并刷新后,左侧导航栏一直处于加载状态的问题\r\n- 修复混合模式导航下调用 `initRouter` 函数导致左侧导航内存溢出问题\r\n- 修复关闭左侧、右侧、其他、全部标签页操作时缓存页面并没有销毁问题\r\n- 修复路由通过 `query` 或 `params` 传参,开启缓存后关闭标签页缓存失效问题\r\n- 修复 `params` 路由传参模式下,面包屑无法找到父级路径问题\r\n\r\n### 🍏 Perf\r\n\r\n- 优化 `RePureTableBar` 组件的 `buttons` 具名插槽\r\n- 优化导航样式以及菜单折叠动画\r\n- 优化菜单名称右侧的额外图标,使其支持更多图标渲染模式\r\n- 优化 `logo` 图和文字布局以及统一配置\r\n- 路由信息 `showLink` 设置成 `false` 后,当前路由信息不添加到标签页\r\n- 导出 `addPathMatch` 函数\r\n- `pinia` 中所有 `getters` 改为官方推荐写法,`this` 改成 `state` 可自动推导类型\r\n- 适配最新版 `pure-table` 的 `api`\r\n- 忽略 `sourcemap-codec` 和 `stable` 依赖包的 `deprecation` 警告\r\n- 从 `tsconfig.json` 文件中移除 `"incremental": true`\r\n- 更新 `stylelint` 以及相关配置至最新,强化样式校验\r\n- 面包屑去首页化,根据选择的菜单对应显示,首页不在固定到面包屑里,并优化面包屑页面的路由监听'
},
{
created_at: "2022-12-26T06:31:04Z",
published_at: "2022-12-26T06:32:38Z",
body: "# 3.9.7 (2022-12-26)\r\n\r\n### 🍏 Perf\r\n\r\n- 使用 `path.posix.resolve` 替代 `path.resolve` 避免 `windows` 环境下使用 `electron` 出现盘符问题\r\n- 默认关闭 `CachingAsyncRoutes` 动态路由缓存本地,使其在开发环境下调试更方便,不用每次修改动态路由都要先清空本地缓存的动态路由,更推荐在生产环境开启"
},
{
created_at: "2022-12-19T04:14:18Z",
published_at: "2022-12-19T04:15:41Z",
body: "# 3.9.6 (2022-12-19)\r\n\r\n### 🎫 Chores\r\n\r\n- 升级 `vite4` 版本\r\n\r\n### 🐞 Bug fixes\r\n\r\n- 修复 `tailwind.css` 错误的引入方式导致 `vite` 的 `hmr` 慢的问题\r\n\r\n### 🍏 Perf\r\n\r\n- 更新 [@pureadmin/theme](https://github.com/pure-admin/pure-admin-theme) 至最新版,带来更友好的类型提示\r\n- 优化 [PureTableBar](https://github.com/xiaoxian521/vue-pure-admin/tree/main/src/components/RePureTableBar) 组件\r\n- 优化系统管理页面业务代码,带来更好的代码参考"
},
{
created_at: "2022-12-13T06:19:31Z",
published_at: "2022-12-13T06:20:30Z",
body: "# 3.9.5 (2022-12-13)\r\n\r\n### ✔️ refactor\r\n\r\n- 完全移除了 `lodash` 和其相关库\r\n [点击此处查看为什么移除?如何自行集成?](https://yiming_chang.gitee.io/pure-admin-doc/pages/FAQ/#%E5%B9%B3%E5%8F%B0%E5%9C%A8-v3-9-5-%E7%89%88%E6%9C%AC%E5%AE%8C%E5%85%A8%E7%A7%BB%E9%99%A4%E4%BA%86-lodash-%E5%92%8C%E5%85%B6%E7%9B%B8%E5%85%B3%E5%BA%93-%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A7%BB%E9%99%A4-%E5%A6%82%E4%BD%95%E8%87%AA%E8%A1%8C%E9%9B%86%E6%88%90)\r\n\r\n### 🎫 Feat\r\n\r\n- 添加 `@pureadmin/table` 表格动态列示例\r\n\r\n### 🐞 Bug fixes\r\n\r\n- 修复动态路由 `rank` 问题\r\n- 修复暗黑主题样式问题\r\n\r\n### 🍏 Perf\r\n\r\n- 优化路由 `rank` ,当 `rank` 不存在时,根据顺序自动创建,首页路由永远在第一位"
},
{
created_at: "2022-12-05T05:59:54Z",
published_at: "2022-12-05T06:04:01Z",
body: "# 3.9.4 (2022-12-05)\r\n\r\n### ✔️ refactor\r\n\r\n- 完全移除了 `vxe-table`,移除后,完整版整体打包大小减少 `1.82MB`,首启动时长基本和精简版持平 🐮\r\n [点击此处查看为什么移除?如何自行集成?](https://yiming_chang.gitee.io/pure-admin-doc/pages/FAQ/#%E5%B9%B3%E5%8F%B0%E5%9C%A8-v3-9-4-%E7%89%88%E6%9C%AC%E5%AE%8C%E5%85%A8%E7%A7%BB%E9%99%A4%E4%BA%86-vxe-table-%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A7%BB%E9%99%A4-%E5%A6%82%E4%BD%95%E8%87%AA%E8%A1%8C%E9%9B%86%E6%88%90)\r\n\r\n### 🎫 Feat\r\n\r\n- 添加 `@pureadmin/table` 表格选择器(单选、多选)示例"
},
{
created_at: "2022-12-04T08:45:47Z",
published_at: "2022-12-04T08:46:59Z",
body: "# 3.9.3 (2022-12-04)\r\n\r\n### 🎫 Feat\r\n\r\n- 添加 `@pureadmin/table` 分页和加载动画示例\r\n\r\n### 🐞 Bug fixes\r\n\r\n- 修复开启 `CachingAsyncRoutes` 后,存入本地存储的动态路由改变造成刷新页面空白的问题\r\n- 修复菜单折叠后 `Tooltip` 显示异常\r\n\r\n### 🍏 Perf\r\n\r\n- 扩展本地图标使用方式,完整版首启动再次减少 `13` 个请求\r\n- 当菜单加载慢时,添加 `loading` 动画,优化用户体验\r\n- 主题初始化放在 `onBeforeMount` 里,避免初始化页面闪烁"
},
{
created_at: "2022-12-03T07:02:17Z",
published_at: "2022-12-03T07:03:09Z",
body: "# 3.9.2 (2022-12-03)\r\n\r\n### 🍏 Perf\r\n\r\n- 全局覆盖 `element-plus` 的 `el-dialog`、`el-drawer`、`el-message-box`、`el-notification` 组件右上角关闭图标的样式,使其表现更鲜明 [具体代码修改记录](https://github.com/xiaoxian521/vue-pure-admin/commit/c80818d792276666aaea4b18413a0f08777f2ed1)\r\n- 打包输出信息兼容不同打包输出路径\r\n- 优化一些动画"
},
{
created_at: "2022-12-02T11:32:48Z",
published_at: "2022-12-02T11:33:45Z",
body: "# 3.9.1 (2022-12-02)\r\n\r\n### 🎫 Feat\r\n\r\n- 添加 `CachingAsyncRoutes` 是否开启动态路由缓存本地的全局配置,默认 `true`\r\n- 添加 `TooltipEffect` 全局配置,可配置平台主体所有 `el-tooltip` 的 `effect` 属性,默认 `light`,不影响业务代码\r\n- 添加目录、菜单文字超出显示 `Tooltip` 文字提示演示\r\n\r\n### 🍏 Perf\r\n\r\n- 优化 `initRouter` 方法,兼容 `sso` 场景\r\n- 面包屑动画样式优化"
},
{
created_at: "2022-11-30T06:11:08Z",
published_at: "2022-11-30T06:12:32Z",
body: "# 3.9.0 (2022-11-30)\r\n\r\n### 🐞 Bug fixes\r\n\r\n- 修复退出全屏时文字过长导致页面出现滚动条后带来的抖动问题\r\n- 修复一些类型错误\r\n\r\n### 🍏 Perf\r\n\r\n- perf: 首屏加载大优化,对比 `3.9.0` 之前版本,首屏请求减少 `71` 个,首屏加载资源减少 `4.1 MB`"
},
{
created_at: "2022-11-27T17:25:43Z",
published_at: "2022-11-27T17:27:59Z",
body: "# 3.8.7 (2022-11-28)\r\n\r\n### 🍏 Perf\r\n\r\n- perf: 打包大优化,请务必升级!使用 `unplugin-vue-define-options` 替换 `unplugin-vue-macros` ,打包速度提升数倍,使用 `unplugin-vue-macros` 以性能中等偏上的 `mac` 为例完整版打包时长为 `6` 分钟 😭,使用 `unplugin-vue-define-options` 替换后,相同电脑下打包时长为 `50` 秒 ☺️"
},
{
created_at: "2022-11-27T08:34:46Z",
published_at: "2022-11-27T08:35:49Z",
body: "# 3.8.6 (2022-11-27)\r\n\r\n### 🎫 Feat\r\n\r\n- 添加 `message` 消息提示函数,兼容 `Element Plus` 和 `Ant Design` 两种 `Message` 样式风格,使用和打包大小成本极低并适配暗黑模式,真香 😂\r\n\r\n### 🍏 Perf\r\n\r\n- perf: 无需安装 `@vue/runtime-core` ,兼容所有 `element-plus` 组件的 `volar` 提示"
},
{
created_at: "2022-11-26T16:03:19Z",
published_at: "2022-11-26T16:04:41Z",
body: "# 3.8.5 (2022-11-26)\r\n\r\n### 🍏 Perf\r\n\r\n- 大优化,移除 `@pureadmin/components` 并采用兼容写法,平台打包大小在未启用压缩前对比优化前减少 `0.4` MB , 首屏请求减少 `2.3` MB 的资源,这对于 [精简版](https://github.com/xiaoxian521/pure-admin-thin) 来说是非常大的优化,精简版已经同步代码"
},
{
created_at: "2022-11-26T07:07:07Z",
published_at: "2022-11-26T07:08:11Z",
body: "# 3.8.0 (2022-11-26)\r\n\r\n### 🎫 Feat\r\n\r\n- 添加 `@pureadmin/table` 多种数据格式(深层结构)示例\r\n- 添加 `@pureadmin/table` 图像预览示例\r\n- 添加 `@pureadmin/table` 行、列拖拽示例\r\n- 添加 `@pureadmin/table` 右键菜单示例\r\n- 添加 `@pureadmin/table` 导出 `Excel` 示例\r\n- 添加 `@pureadmin/table` 编辑单元格示例\r\n- 添加 `@pureadmin/table` 水印示例\r\n- 添加 `@pureadmin/table` 打印示例\r\n- 添加 `@pureadmin/table` 内嵌 `echarts` 图表示例\r\n- 添加 `svgo` 压缩平台所有 `svg` 文件,减少体积\r\n\r\n### 🍏 Perf\r\n\r\n- 静态路由平台自动导入,无需手动引入\r\n- 更完善的全局类型提示\r\n- 优化 `vite` 依赖预构建在平台里的配置,页面切换加载速度显著加快"
},
{
created_at: "2022-11-21T17:00:04Z",
published_at: "2022-11-21T17:00:42Z",
body: "# 3.7.1 (2022-11-22)\r\n\r\n### 🔥 hotfix\r\n\r\n- 修复在未开启标签页缓存时退出登录,可能存在标签页未重置的问题"
},
{
created_at: "2022-11-21T09:14:22Z",
published_at: "2022-11-21T09:15:24Z",
body: "# 3.7.0 (2022-11-21)\r\n\r\n### ✔️ refactor\r\n\r\n- 使用 `intro.js` 替换 `driver.js`\r\n\r\n### 🎫 Feat\r\n\r\n- 添加前端单点登录,测试地址 https://yiming_chang.gitee.io/vue-pure-admin/#/pure-table/index?username=sso&roles=admin&accessToken=eyJhbGciOiJIUzUxMiJ9.admin\r\n- 为 [@pureadmin/table](https://github.com/xiaoxian521/pure-admin-table) 添加更多的示例和 `element-plus` 的 [table](https://element-plus.org/zh-CN/component/table.html) 示例保持一致\r\n- 丰富水印功能页面(支持自定义各种颜色、阴影、文字、额外属性、设置不可删除水印以及给指定元素设置水印)\r\n- 优化菜单,添加 `MenuArrowIconNoTransition` 全局配置,在 `public/serverConfig.json` 中配置即可,对于出现左侧菜单模式,菜单展开卡顿的可设置 `MenuArrowIconNoTransition: true` 即可解决\r\n- 更换表单设计器组件演示\r\n\r\n### 🐞 Bug fixes\r\n\r\n- 修复页内菜单带参互相跳转,标签没有选中高亮\r\n\r\n### 🍏 Perf\r\n\r\n- 删除已废弃的 `$baseUrl`\r\n- 兼容引入某个库导致 `global is not defined` 报错,将 `src/utils/globalPolyfills.ts` 文件引入 `src/main.ts` 即可解决\r\n- 删除 `@vitejs/plugin-legacy``vue3` 无法通过任何工具使其支持 `ie`"
},
{
created_at: "2022-11-10T04:17:05Z",
published_at: "2022-11-10T04:18:18Z",
body: "# 3.6.4 (2022-11-10)\r\n\r\n### 🎫 Feat\r\n\r\n- 菜单图标 `icon` 支持使用在线图标\r\n\r\n### 🐞 Bug fixes\r\n\r\n- 修复 `vxe-button` 鼠标覆盖后字体颜色问题以及一些别的样式问题\r\n\r\n### 🍏 Perf\r\n\r\n- 优化路由守卫,如果已经登录并存在登录信息后不能跳转到路由白名单,而是继续保持在当前页面\r\n- 将 `baseURL` 和全局环境代理删除,可直接在 `vite.config.ts` 编写,即方便又支持多个代理地址"
},
{
created_at: "2022-11-01T08:18:12Z",
published_at: "2022-11-01T08:19:22Z",
body: "# 3.6.3 (2022-11-01)\r\n\r\n### 🎫 Feat\r\n\r\n- 静态资源分类打包\r\n- 添加弹幕组件 `demo`\r\n\r\n### 🐞 Bug fixes\r\n\r\n- 修复 `tailwindcss` 最新版新增的 `collapse` 属性与平台 `class` 类名冲突\r\n- 修复当 `token` 过期后,如果页面有多个请求会重复刷新 `token`"
},
{
created_at: "2022-10-27T04:58:44Z",
published_at: "2022-10-27T04:59:32Z",
body: "# 3.6.2 (2022-10-27)\r\n\r\n### ✔️ refactor\r\n\r\n- 使用`@/`别名替换`/@/`别名"
},
{
created_at: "2022-10-26T18:42:33Z",
published_at: "2022-10-26T18:43:31Z",
body: "# 3.6.1 (2022-10-27)\r\n\r\n### 🎫 Feat\r\n\r\n- 添加打包是否启动`cdn`替换本地库配置,默认`false`不启动\r\n- 添加打包构建可选`gzip`与`brotli`压缩模式\r\n\r\n### 🐞 Bug fixes\r\n\r\n- 修复`title`过长显示样式问题\r\n- 修复路由中父级`name`不应和子级`name`重复,会造成重定向跳转`404`问题\r\n\r\n### 🍏 Perf\r\n\r\n- 升级`axios`至最新版"
},
{
created_at: "2022-10-25T05:07:18Z",
published_at: "2022-10-25T05:08:18Z",
body: "# 3.6.0 (2022-10-25)\r\n\r\n### 🎫 Feat\r\n\r\n- 添加文件下载`demo`\r\n- 添加打字机组件`demo`\r\n- 添加`json`编辑器`demo`\r\n\r\n### ✔️ refactor\r\n\r\n- 重构权限模块,采用目前最常用的`RBAC`Role-Based Access List: 基于角色的权限控制( 用户 -> 角色 -> 权限 ),并更新页面权限和按钮权限`demo`示例,按钮权限支持三种操作模式(组件方式判断权限、函数方式判断权限、指令方式判断权限)\r\n\r\n### 🐞 Bug fixes\r\n\r\n- 修复清空缓存并返回登录页时未清空主题\r\n- 修复`horizontal`模式下`menu`在生产环境显示问题\r\n- 修复`mix`混合模式导航在生产环境左侧菜单一定机率不显示的问题\r\n- `token`过期后调用刷新`token`接口会无限循环的问题\r\n\r\n### 🍏 Perf\r\n\r\n- 从`tailwind.css`中移除不常用的`@apply`\r\n- 使用`/** */`替换`//`注释,对编辑器的智能提示更友好\r\n- 优化登录回车事件\r\n- 简化了一些函数,剔除了无用函数,优化了页面加载速度"
},
{
created_at: "2022-09-10T13:44:17Z",
published_at: "2022-09-10T13:46:11Z",
body: "# 3.5.0 (2022-9-10)\r\n\r\n### 🎫 Feat\r\n\r\n- 添加 `cssnano` ,打包时压缩 `css` 体积\r\n- 添加 `element-plus` 无缝滚动 `Table` 页面 demo\r\n- 开启 `vscode` 括号对指南\r\n\r\n### ✔️ refactor\r\n\r\n- 使用 `tailwindcss` 替换 `unocss`,新增 `tailwindcss` [使用文档](http://yiming_chang.gitee.io/pure-admin-doc/pages/39156f/)\r\n\r\n### 🐞 Bug fixes\r\n\r\n- `token` 过期,刷新死循环\r\n\r\n### 🍏 Perf\r\n\r\n- 重置路由时,清空缓存页面"
},
{
created_at: "2022-08-23T02:31:44Z",
published_at: "2022-08-23T02:32:49Z",
body: "# 3.4.6 (2022-8-23)\r\n\r\n### 🐞 Bug fixes\r\n\r\n- `process` is not defined in path\r\n- 修复动态路由`children`为空数组时报错\r\n- 修复`iframe`加载失败"
},
{
created_at: "2022-08-22T12:21:53Z",
published_at: "2022-08-22T12:23:21Z",
body: "# 3.4.5 (2022-8-22)\r\n\r\n### 🐞 Bug fixes\r\n\r\n- 修复本地响应式存储对象设置问题"
},
{
created_at: "2022-08-22T09:21:18Z",
published_at: "2022-08-22T09:22:26Z",
body: "# 3.4.0 (2022-8-22)\r\n\r\n### 🍏 Perf\r\n\r\n- 优化路由\r\n- 优化移动端兼容性\r\n- 优化路由传参(`query`、`params` 方式刷新页面不需要再开启标签页缓存也能保留参数在`url`和`标签页`上)"
},
{
created_at: "2022-08-19T07:29:06Z",
published_at: "2022-08-19T07:31:08Z",
body: "# 3.3.5 (2022-8-19)\r\n\r\n### 🎫 Feat\r\n\r\n- 将 `element-plus` 的 `Table` 二次封装到[@pureadmin/table](https://github.com/xiaoxian521/pure-admin-table),提供灵活的配置项并集成到平台里\r\n- 将 `element-plus` 的 `Descriptions` 二次封装到[@pureadmin/descriptions](https://github.com/xiaoxian521/pure-admin-descriptions),提供灵活的配置项并集成到平台里\r\n- 将平台的大部分工具以及 hooks 都集中到[@pureadmin/utils](https://pure-admin-utils-docs.vercel.app/),并删除集中到这个库里的代码,减少平台体积\r\n- 添加[unplugin-vue-define-options](https://www.npmjs.com/package/unplugin-vue-define-options)插件,页面可直接写 `defineOptions({name: 自定义名称})`\r\n- 添加项目文件、语言分析工具 [cloc](https://www.npmjs.com/package/cloc)\r\n- 添加登陆页国际化\r\n- 添加完整路由配置表类型声明\r\n- 添加虚拟列表页面 demo\r\n- 添加 `PDF` 预览页面 demo\r\n- 添加导出 `execl` 页面 demo\r\n- 添加无 `Layout` 的空白页面 demo\r\n\r\n### ✔️ refactor\r\n\r\n- 重构主题色,适配 `element-plus` 暗黑模式(同时也解决了 `3.3.0` 及更低版本中同样的元素 `css` 被多次覆盖,导致样式不好调试的问题)\r\n- 重构路由重置功能\r\n\r\n### 🍏 Perf\r\n\r\n- 兼容项目存放目录以中文命名,但我们真心不推荐中文命名,因为可能某个库没有对中文路径做转义处理,导致项目奔溃\r\n- 优化接口类型\r\n\r\n### 🐞 Bug fixes\r\n\r\n- 修复路由 `showlink` 为 `false` 的异步路由,刷新后不显示\r\n- 修复当没有 `icon` 时,垂直导航菜单折叠后文字被隐藏"
},
{
created_at: "2022-05-11T07:51:38Z",
published_at: "2022-05-11T07:52:31Z",
body: "# 3.3.0 (2022-5-11)\r\n\r\n### 🎫 Feat\r\n\r\n- 添加用户管理页面 demo\r\n- 添加角色管理页面 demo\r\n- 添加部门管理页面 demo\r\n- 添加卡片列表页面 demo\r\n- 集成表单设计器\r\n- 新增`PPT`demo\r\n- 在功能菜单中新增防抖截流 demo\r\n- 升级`wangeditorV5`(并支持国际化和自定义主题)\r\n- 集成`tauri`版本\r\n- 新增条形码功能\r\n- 新增二维码功能\r\n- 使用`element-plus`中的`Cascader`级联选择器编写中国省市区三级、二级联动 demo\r\n- 集成`Swiper`插件\r\n- 路由支持传`component`,代表组件路径\r\n- 添加预发布打包模式\r\n- 添加关闭某个标签的[hooks](https://github.com/xiaoxian521/vue-pure-admin/commit/5e8723a031923e79f507e5a17151d3bd88a51523)\r\n\r\n### ✔️ refactor\r\n\r\n- 重构登陆页,更偏向实际业务场景\r\n- 使用`unocss`替换`windicss``unocss`开发环境下性能更好,没有内存泄露,而且`api`使用上兼容`windicss`\r\n\r\n### 🍏 Perf\r\n\r\n- 优化平台的`split-pane`组件样式\r\n- 优化国际化,路由不再传`i18n`字段,平台自动读取根目录`locales`文件夹下文件进行国际化匹配\r\n- 优化图标选择器\r\n- 优化`layout`显示用户信息[commit](https://github.com/xiaoxian521/vue-pure-admin/commit/56f9dc85e7fbe0637605c43577c794de9f8968aa)\r\n\r\n### 🐞 Bug fixes\r\n\r\n- 修复路由初始化问题Cannot access 'constantRoutes' before initialization"
},
{
created_at: "2022-03-21T16:25:55Z",
published_at: "2022-03-21T16:27:00Z",
body: "# 3.2.0 (2022-3-22)\r\n\r\n### 🎫 Feat\r\n\r\n- 图标选择组件\r\n- 菜单搜索功能\r\n- 添加结果页面\r\n- 扩展`element-plus`时间线组件\r\n- 扩展`element-plus`树组件,支持连接线\r\n- 添加树形选择器,支持单选和多选\r\n\r\n### 🍏 Perf\r\n\r\n- 优化错误页面 UI\r\n- 优化国际化功能\r\n- 优化路由`rank`排序,兼容路由`meta`中`rank`字段值为`null`的情况\r\n\r\n### 🐞 Bug fixes\r\n\r\n- 修复菜单展开折叠在部分电脑出现卡顿的情况"
},
{
created_at: "2022-03-03T14:57:11Z",
published_at: "2022-03-03T14:57:55Z",
body: "# 3.1.0 (2022-3-3)\r\n\r\n### 🎫 Feat\r\n\r\n- iframe 支持动态加载\r\n- 水印示例\r\n- 打印示例图片、表格、echarts\r\n- 添加运行、打包信息, 使用`lodash-unified`替换`lodash-es`,`lodash-unified`支持`ESM`同时兼容`CJS`\r\n\r\n### 🐞 Bug fixes\r\n\r\n- 修复在一个菜单页面内单独跳转到另一个菜单页面,路由页面跳转了但是标签页不显示的情况\r\n- 修复后台返回动态三级及以上的路由,出现菜单与页面不对应的情况"
},
{
created_at: "2022-02-14T15:19:32Z",
published_at: "2022-02-14T15:20:32Z",
body: "# 3.0 (2022-2-14)\r\n\r\n### 🎫 Feat\r\n\r\n- 添加混合导航\r\n\r\n### 🐞 Bug fixes\r\n\r\n- 修复标签页 bug"
},
{
created_at: "2022-02-05T09:36:21Z",
published_at: "2022-02-05T09:38:09Z",
body: "# 2.9.0(2022-2-5)\r\n\r\n### 🎫 Feat\r\n\r\n- 添加打包大小分析,命令`pnpm report`\r\n\r\n### 🍏 Perf\r\n\r\n- 采用`iconify`按需引入图标,优化图标大小,减少网络请求\r\n- 优化路由,路由可不传`showLink: true`,默认显示"
},
{
created_at: "2022-01-21T08:46:48Z",
published_at: "2022-01-21T08:49:38Z",
body: "# 2.8.5(2022-1-21)\r\n\r\n### 🎫 Feat\r\n\r\n- 添加 `WindiCSS` 支持\r\n- 添加线上环境删 console 插件`vite-plugin-remove-console`\r\n\r\n### ✔️ refactor\r\n\r\n- 使用`@iconify-icons/ep`替换`@element-plus/icons-vue`"
},
{
created_at: "2022-01-04T11:52:05Z",
published_at: "2022-01-04T11:53:17Z",
body: "# 2.8.0(2022-1-4)\r\n\r\n### 🎫 Feat\r\n\r\n- 添加暗黑主题\r\n- 添加 element-plus 自定义主题\r\n- 添加引导页\r\n\r\n### 🍏 Perf\r\n\r\n- 优化国际化,兼容 vscode 插件 i18n Ally 智能提醒\r\n- 优化后端返回路由结构\r\n- 优化本地存储,内置四个键`responsive-configure`、`responsive-locale`、`responsive-layout`、`responsive-tags`,分别为基本配置、国际化配置、布局配置、标签页持久化配置"
},
{
created_at: "2021-12-18T05:56:21Z",
published_at: "2021-12-18T05:57:55Z",
body: "# 2.7.0(2021-12-18)\r\n\r\n### 🎫 Feat\r\n\r\n- 新增标签页复用\r\n- 新增消息提醒模版\r\n- 新增前端菜单树结构例子\r\n- 重构路由,优化权限模块,带来更方便的体验\r\n- 重构 env 环境和 http 请求,带来更方便的体验\r\n- 目前平台的标签页强制关联了本地存储,下一步标签页默认放到内存中并支持可配置持久化标签页\r\n- 导航菜单图标支持 fontawesome、iconfont、remixicon、element-plus/icons、自定义 svg\r\n- 更新 font-awesome 到 5.0 版本,因为 5.0 以下的版本官方不再维护,但平台依旧会兼容 font-awesome4 版本\r\n\r\n### 🍏 Perf\r\n\r\n- 优化标签页,带来更好的交互体验\r\n- 路由 title 支持直接写中文,可脱离国际化\r\n- 路由历史模式从 env 读取并支持 base 参数\r\n- 打包后的文件提供传统浏览器兼容性支持,配置 VITE_LEGACY 为 true"
},
{
created_at: "2021-11-10T05:30:48Z",
published_at: "2021-11-10T05:33:37Z",
body: "# 2.6.0(2021-11-10)\r\n\r\n### 🎫 Feat\r\n\r\n- 重构导航主题色,支持多种配色\r\n- 重构登录页,插画风格\r\n\r\n### 🍏 Perf\r\n\r\n- 优化导航样式\r\n- 剔除导航强依赖 vxe-table\r\n- 同步更新 element-plus使用 SVG Icon 替换 Font Icon"
},
{
created_at: "2021-10-14T09:50:03Z",
published_at: "2021-10-14T09:52:01Z",
body: "# 2.1.0(2021-10-14)\r\n\r\n### 🎫 Feat\r\n\r\n- 路由动画(每个路由都可添加不同动画)\r\n- 额外图标(比如这个是新加的页面,路由菜单右上角显示个新图标)\r\n- 抽离默认配置选项\r\n- 完善类型文件\r\n\r\n### 🐞 Bug fixes\r\n\r\n- 修复 element-plus 国际化使用问题\r\n- 修复路由问题\r\n- 修复导航适配问题"
},
{
created_at: "2021-09-28T18:32:30Z",
published_at: "2021-09-28T18:35:41Z",
body: "# 2.0.1(2021-9-29)\r\n\r\n### 🎫 Feat\r\n\r\n- 添加 horizontal 水平模式导航"
},
{
created_at: "2021-04-13T10:53:29Z",
published_at: "2021-04-13T10:57:50Z",
body: "# 2.0.0(2021-4-13)\r\n\r\n### 🎫 Chores\r\n\r\n- 发布 2.0.0 版本"
}
]
}
};
}
}
] as MockMethod[];

View File

@@ -1,212 +1,7 @@
import { MockMethod } from "vite-plugin-mock";
export default [
{
url: "/role",
method: "post",
response: () => {
return {
success: true,
data: {
list: [
{
createTime: 1609837428000,
updateTime: 1645477701000,
creator: "admin",
updater: "",
deleted: false,
tenantId: 1,
id: 1,
name: "超级管理员",
code: "super_admin",
sort: 1,
status: 0,
type: 1,
remark: "超级管理员",
dataScope: 1,
dataScopeDeptIds: null
},
{
createTime: 1609837428000,
updateTime: 1645477700000,
creator: "admin",
updater: "",
deleted: false,
tenantId: 1,
id: 2,
name: "普通角色",
code: "common",
sort: 2,
status: 0,
type: 1,
remark: "普通角色",
dataScope: 2,
dataScopeDeptIds: null
},
{
createTime: 1609912175000,
updateTime: 1647698441000,
creator: "",
updater: "1",
deleted: false,
tenantId: 1,
id: 101,
name: "测试账号",
code: "test",
sort: 0,
status: 0,
type: 2,
remark: "132",
dataScope: 1,
dataScopeDeptIds: []
}
],
total: 3
}
};
}
},
{
url: "/dept",
method: "post",
response: () => {
return {
success: true,
data: [
{
name: "杭州总公司",
type: 1, // 1 公司 2 分公司 3 部门
parentId: 0,
sort: 0,
leaderUserId: 1,
phone: "15888888888",
email: "ry@qq.com",
status: 0,
id: 100,
createTime: 1609837427000,
remark: "备注、备注、备注、备注、备注、备注、备注"
},
{
name: "郑州分公司",
type: 2,
parentId: 100,
sort: 1,
leaderUserId: 104,
phone: "15888888888",
email: "ry@qq.com",
status: 0,
id: 101,
createTime: 1609837427000,
remark: "备注、备注、备注、备注、备注、备注、备注"
},
{
name: "研发部门",
type: 3,
parentId: 101,
sort: 1,
leaderUserId: 104,
phone: "15888888888",
email: "ry@qq.com",
status: 0,
id: 103,
createTime: 1609837427000,
remark: "备注、备注、备注、备注、备注、备注、备注"
},
{
name: "市场部门",
type: 3,
parentId: 102,
sort: 1,
leaderUserId: null,
phone: "15888888888",
email: "ry@qq.com",
status: 0,
id: 108,
createTime: 1609837427000,
remark: "备注、备注、备注、备注、备注、备注、备注"
},
{
name: "深圳分公司",
type: 2,
parentId: 100,
sort: 2,
leaderUserId: null,
phone: "15888888888",
email: "ry@qq.com",
status: 0,
id: 102,
createTime: 1609837427000,
remark: "备注、备注、备注、备注、备注、备注、备注"
},
{
name: "市场部门",
type: 3,
parentId: 101,
sort: 2,
leaderUserId: null,
phone: "15888888888",
email: "ry@qq.com",
status: 1,
id: 104,
createTime: 1609837427000,
remark: "备注、备注、备注、备注、备注、备注、备注"
},
{
name: "财务部门",
type: 3,
parentId: 102,
sort: 2,
leaderUserId: null,
phone: "15888888888",
email: "ry@qq.com",
status: 0,
id: 109,
createTime: 1609837427000,
remark: "备注、备注、备注、备注、备注、备注、备注"
},
{
name: "测试部门",
type: 3,
parentId: 101,
sort: 3,
leaderUserId: null,
phone: "15888888888",
email: "ry@qq.com",
status: 0,
id: 105,
createTime: 1609837427000,
remark: "备注、备注、备注、备注、备注、备注、备注"
},
{
name: "财务部门",
type: 3,
parentId: 101,
sort: 4,
leaderUserId: 103,
phone: "15888888888",
email: "ry@qq.com",
status: 1,
id: 106,
createTime: 1609837427000,
remark: "备注、备注、备注、备注、备注、备注、备注"
},
{
name: "运维部门",
type: 3,
parentId: 101,
sort: 5,
leaderUserId: null,
phone: "15888888888",
email: "ry@qq.com",
status: 0,
id: 107,
createTime: 1609837427000,
remark: "备注、备注、备注、备注、备注、备注、备注"
}
]
};
}
},
// 用户
{
url: "/user",
method: "post",
@@ -225,7 +20,7 @@ export default [
sex: 0,
id: 1,
status: 0,
createTime: 1609837427000,
createTime: 1605456000000,
dept: {
id: 103,
name: "研发部门"
@@ -241,7 +36,7 @@ export default [
sex: 0,
id: 100,
status: 1,
createTime: 1609981637000,
createTime: 1605456000000,
dept: {
id: 104,
name: "市场部门"
@@ -257,7 +52,7 @@ export default [
sex: 1,
id: 103,
status: 1,
createTime: 1610553035000,
createTime: 1605456000000,
dept: {
id: 106,
name: "财务部门"
@@ -273,7 +68,7 @@ export default [
sex: 0,
id: 104,
status: 0,
createTime: 1611166433000,
createTime: 1605456000000,
dept: {
id: 107,
name: "运维部门"
@@ -284,5 +79,190 @@ export default [
}
};
}
},
// 角色
{
url: "/role",
method: "post",
response: ({ body }) => {
let list = [
{
createTime: 1605456000000, // 时间戳毫秒ms
updateTime: 1684512000000,
creator: "admin",
id: 1,
name: "超级管理员",
code: "admin",
status: 1, // 状态 1 启用 0 停用
remark: "超级管理员拥有最高权限"
},
{
createTime: 1605456000000,
updateTime: 1684512000000,
creator: "admin",
id: 2,
name: "普通角色",
code: "common",
status: 1,
remark: "普通角色拥有部分权限"
}
];
list = list.filter(item => item.name.includes(body?.name));
list = list.filter(item =>
String(item.status).includes(String(body?.status))
);
if (body.code) list = list.filter(item => item.code === body.code);
return {
success: true,
data: {
list,
total: list.length, // 总条目数
pageSize: 10, // 每页显示条目个数
currentPage: 1 // 当前页数
}
};
}
},
// 部门
{
url: "/dept",
method: "post",
response: () => {
return {
success: true,
data: [
{
name: "杭州总公司",
parentId: 0,
id: 100,
sort: 0,
phone: "15888888888",
principal: "@cname()",
email: "@email",
status: 1, // 状态 1 启用 0 停用
type: 1, // 1 公司 2 分公司 3 部门
createTime: 1605456000000,
remark: "@cparagraph(1, 3)"
},
{
name: "郑州分公司",
parentId: 100,
id: 101,
sort: 1,
phone: "15888888888",
principal: "@cname()",
email: "@email",
status: 1,
type: 2,
createTime: 1605456000000,
remark: "@cparagraph(1, 3)"
},
{
name: "研发部门",
parentId: 101,
id: 103,
sort: 1,
phone: "15888888888",
principal: "@cname()",
email: "@email",
status: 1,
type: 3,
createTime: 1605456000000,
remark: "@cparagraph(1, 3)"
},
{
name: "市场部门",
parentId: 102,
id: 108,
sort: 1,
phone: "15888888888",
principal: "@cname()",
email: "@email",
status: 1,
type: 3,
createTime: 1605456000000,
remark: "@cparagraph(1, 3)"
},
{
name: "深圳分公司",
parentId: 100,
id: 102,
sort: 2,
phone: "15888888888",
principal: "@cname()",
email: "@email",
status: 1,
type: 2,
createTime: 1605456000000,
remark: "@cparagraph(1, 3)"
},
{
name: "市场部门",
parentId: 101,
id: 104,
sort: 2,
phone: "15888888888",
principal: "@cname()",
email: "@email",
status: 1,
type: 3,
createTime: 1605456000000,
remark: "@cparagraph(1, 3)"
},
{
name: "财务部门",
parentId: 102,
id: 109,
sort: 2,
phone: "15888888888",
principal: "@cname()",
email: "@email",
status: 1,
type: 3,
createTime: 1605456000000,
remark: "@cparagraph(1, 3)"
},
{
name: "测试部门",
parentId: 101,
id: 105,
sort: 3,
phone: "15888888888",
principal: "@cname()",
email: "@email",
status: 0,
type: 3,
createTime: 1605456000000,
remark: "@cparagraph(1, 3)"
},
{
name: "财务部门",
parentId: 101,
id: 106,
sort: 4,
phone: "15888888888",
principal: "@cname()",
email: "@email",
status: 1,
type: 3,
createTime: 1605456000000,
remark: "@cparagraph(1, 3)"
},
{
name: "运维部门",
parentId: 101,
id: 107,
sort: 5,
phone: "15888888888",
principal: "@cname()",
email: "@email",
status: 0,
type: 3,
createTime: 1605456000000,
remark: "@cparagraph(1, 3)"
}
]
};
}
}
] as MockMethod[];

View File

@@ -1,11 +1,11 @@
{
"name": "vue-pure-admin",
"version": "4.0.0",
"version": "4.5.0",
"private": true,
"scripts": {
"dev": "NODE_OPTIONS=--max-old-space-size=4096 vite",
"serve": "pnpm dev",
"build": "rimraf dist && NODE_OPTIONS=--max-old-space-size=8192 vite build",
"build": "rimraf dist && NODE_OPTIONS=--max-old-space-size=8192 vite build && generate-version-file",
"build:staging": "rimraf dist && vite build --mode staging",
"report": "rimraf dist && vite build",
"preview": "vite preview",
@@ -13,7 +13,7 @@
"typecheck": "tsc --noEmit && vue-tsc --noEmit --skipLibCheck",
"svgo": "svgo -f src/assets/svg -o src/assets/svg",
"cloc": "NODE_OPTIONS=--max-old-space-size=4096 cloc . --exclude-dir=node_modules --exclude-lang=YAML",
"clean:cache": "rm -rf node_modules && rm -rf .eslintcache && pnpm install",
"clean:cache": "rimraf node_modules && rimraf .eslintcache && pnpm install",
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock,build}/**/*.{vue,js,ts,tsx}\" --fix",
"lint:prettier": "prettier --write \"src/**/*.{js,ts,json,tsx,css,scss,vue,html,md}\"",
"lint:stylelint": "stylelint --cache --fix \"**/*.{html,vue,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
@@ -31,118 +31,121 @@
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@howdyjs/mouse-menu": "^2.0.7",
"@logicflow/core": "^1.2.5",
"@logicflow/extension": "^1.2.5",
"@logicflow/core": "^1.2.9",
"@logicflow/extension": "^1.2.9",
"@pureadmin/descriptions": "^1.1.1",
"@pureadmin/table": "^2.1.0",
"@pureadmin/utils": "^1.8.9",
"@vueuse/core": "^10.1.2",
"@vueuse/motion": "2.0.0-beta.12",
"@wangeditor/editor": "^5.1.21",
"@pureadmin/table": "^2.3.2",
"@pureadmin/utils": "^1.9.6",
"@vueuse/core": "^10.2.0",
"@vueuse/motion": "^2.0.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"animate.css": "^4.1.1",
"axios": "^1.4.0",
"china-area-data": "^5.0.1",
"cropperjs": "^1.5.13",
"dayjs": "^1.11.7",
"dayjs": "^1.11.8",
"echarts": "^5.4.2",
"el-table-infinite-scroll": "^3.0.1",
"element-plus": "^2.3.4",
"element-resize-detector": "^1.2.4",
"element-plus": "^2.3.7",
"intro.js": "^7.0.1",
"js-cookie": "^3.0.5",
"jsbarcode": "^3.11.5",
"md-editor-v3": "2.7.2",
"mint-filter": "^4.0.3",
"mitt": "^3.0.0",
"mockjs": "^1.1.0",
"nprogress": "^0.2.0",
"path": "^0.12.7",
"pinia": "^2.0.36",
"pinia": "^2.1.4",
"pinyin-pro": "^3.15.2",
"qrcode": "^1.5.3",
"qs": "^6.11.1",
"qs": "^6.11.2",
"responsive-storage": "^2.2.0",
"sortablejs": "^1.15.0",
"swiper": "^9.3.0",
"swiper": "^9.4.1",
"typeit": "^8.7.1",
"v-contextmenu": "3.0.0",
"vue": "^3.2.47",
"v3-infinite-loading": "^1.2.2",
"version-rocket": "^1.6.7",
"vue": "^3.3.4",
"vue-i18n": "^9.2.2",
"vue-json-pretty": "^2.2.4",
"vue-pdf-embed": "^1.1.6",
"vue-router": "^4.1.6",
"vue-types": "^5.0.2",
"vue-router": "^4.2.2",
"vue-tippy": "^6.2.0",
"vue-types": "^5.1.0",
"vue-virtual-scroller": "2.0.0-beta.7",
"vue3-danmaku": "1.4.0-beta.1",
"vue-waterfall-plugin-next": "^2.2.1",
"vue3-danmaku": "^1.4.0",
"vuedraggable": "^4.1.0",
"xgplayer": "^3.0.2",
"xgplayer": "^3.0.4",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@commitlint/cli": "^17.6.3",
"@commitlint/config-conventional": "^17.6.3",
"@iconify-icons/ep": "^1.2.11",
"@iconify-icons/ri": "^1.2.7",
"@commitlint/cli": "^17.6.6",
"@commitlint/config-conventional": "^17.6.6",
"@iconify-icons/ep": "^1.2.12",
"@iconify-icons/ri": "^1.2.9",
"@iconify/vue": "^4.1.1",
"@intlify/unplugin-vue-i18n": "^0.10.0",
"@pureadmin/theme": "^3.0.0",
"@types/element-resize-detector": "1.1.3",
"@intlify/unplugin-vue-i18n": "^0.11.0",
"@pureadmin/theme": "^3.1.0",
"@types/intro.js": "^5.1.1",
"@types/js-cookie": "^3.0.3",
"@types/mockjs": "^1.0.7",
"@types/node": "^18.15.12",
"@types/node": "^18.16.18",
"@types/nprogress": "0.2.0",
"@types/qrcode": "^1.5.0",
"@types/qs": "^6.9.7",
"@types/sortablejs": "^1.15.1",
"@typescript-eslint/eslint-plugin": "^5.59.2",
"@typescript-eslint/parser": "^5.59.2",
"@vitejs/plugin-vue": "^4.2.1",
"@typescript-eslint/eslint-plugin": "^5.60.0",
"@typescript-eslint/parser": "^5.60.0",
"@vitejs/plugin-vue": "^4.2.3",
"@vitejs/plugin-vue-jsx": "^3.0.1",
"@vue/eslint-config-prettier": "^7.1.0",
"@vue/eslint-config-typescript": "^11.0.3",
"autoprefixer": "^10.4.14",
"cloc": "^2.11.0",
"cssnano": "^6.0.1",
"eslint": "^8.40.0",
"eslint": "^8.43.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "^9.11.1",
"eslint-plugin-vue": "^9.15.1",
"husky": "^8.0.3",
"lint-staged": "^13.2.2",
"picocolors": "^1.0.0",
"postcss": "^8.4.23",
"postcss": "^8.4.24",
"postcss-html": "^1.5.0",
"postcss-import": "^15.1.0",
"postcss-scss": "^4.0.6",
"prettier": "^2.8.7",
"pretty-quick": "3.1.1",
"rimraf": "^5.0.0",
"rollup-plugin-visualizer": "^5.9.0",
"sass": "^1.62.1",
"sass-loader": "^13.2.2",
"stylelint": "^15.6.1",
"prettier": "^2.8.8",
"pretty-quick": "^3.1.3",
"rimraf": "^5.0.1",
"rollup-plugin-visualizer": "^5.9.2",
"sass": "^1.63.6",
"sass-loader": "^13.3.2",
"stylelint": "^15.9.0",
"stylelint-config-html": "^1.1.0",
"stylelint-config-recess-order": "^4.0.0",
"stylelint-config-recess-order": "^4.2.0",
"stylelint-config-recommended": "^12.0.0",
"stylelint-config-recommended-scss": "^11.0.0",
"stylelint-config-recommended-scss": "^12.0.0",
"stylelint-config-recommended-vue": "^1.4.0",
"stylelint-config-standard": "^33.0.0",
"stylelint-config-standard-scss": "^9.0.0",
"stylelint-order": "^6.0.3",
"stylelint-prettier": "^3.0.0",
"stylelint-scss": "^5.0.0",
"stylelint-scss": "^5.0.1",
"svgo": "^3.0.2",
"tailwindcss": "^3.3.2",
"terser": "^5.17.1",
"typescript": "^5.0.4",
"unplugin-vue-define-options": "1.1.6",
"vite": "^4.3.5",
"terser": "^5.18.1",
"typescript": "5.0.4",
"vite": "^4.3.9",
"vite-plugin-cdn-import": "^0.3.5",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-mock": "2.9.6",
"vite-plugin-remove-console": "^2.1.1",
"vite-svg-loader": "^4.0.0",
"vue-eslint-parser": "^9.2.1",
"vue-tsc": "^1.2.0"
"vue-eslint-parser": "^9.3.1",
"vue-tsc": "^1.8.1"
},
"pnpm": {
"peerDependencyRules": {
@@ -154,6 +157,7 @@
},
"allowedDeprecatedVersions": {
"sourcemap-codec": "*",
"w3c-hr-time": "*",
"stable": "*"
}
},

3961
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
{
"Version": "4.0.0",
"Version": "4.5.0",
"Title": "PureAdmin",
"FixedHeader": true,
"HiddenSideBar": false,

View File

@@ -7,10 +7,12 @@
<script lang="ts">
import { defineComponent } from "vue";
import { checkVersion } from "version-rocket";
import { ElConfigProvider } from "element-plus";
import zhCn from "element-plus/lib/locale/lang/zh-cn";
import en from "element-plus/lib/locale/lang/en";
import { ReDialog } from "@/components/ReDialog";
import zhCn from "element-plus/lib/locale/lang/zh-cn";
export default defineComponent({
name: "app",
components: {
@@ -21,6 +23,29 @@ export default defineComponent({
currentLocale() {
return this.$storage.locale?.locale === "zh" ? zhCn : en;
}
},
beforeCreate() {
const { version, name: title } = __APP_INFO__.pkg;
const { VITE_PUBLIC_PATH, MODE } = import.meta.env;
// https://github.com/guMcrey/version-rocket/blob/main/README.zh-CN.md#api
if (MODE === "production") {
// 版本实时更新检测,只作用于线上环境
checkVersion(
// config
{
// 5分钟检测一次版本
pollingTime: 300000,
localPackageVersion: version,
originVersionFileUrl: `${location.origin}${VITE_PUBLIC_PATH}version.json`
},
// options
{
title,
description: "检测到新版本",
buttonText: "立即更新"
}
);
}
}
});
</script>

View File

@@ -12,3 +12,8 @@ type Result = {
export const getCardList = (data?: object) => {
return http.request<Result>("post", "/getCardList", { data });
};
/** 版本日志 */
export const getReleases = () => {
return http.request<Result>("get", "/releases");
};

View File

@@ -5,8 +5,12 @@ type Result = {
data?: {
/** 列表数据 */
list: Array<any>;
/** 总数 */
/** 总条目数 */
total?: number;
/** 每页显示条目个数 */
pageSize?: number;
/** 当前页数 */
currentPage?: number;
};
};

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

BIN
src/assets/user.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -0,0 +1,29 @@
import { ElCol } from "element-plus";
import { h, defineComponent } from "vue";
// 封装element-plus的el-col组件
export default defineComponent({
name: "ReCol",
props: {
value: {
type: Number,
default: 24
}
},
render() {
const attrs = this.$attrs;
const val = this.value;
return h(
ElCol,
{
xs: val,
sm: val,
md: val,
lg: val,
xl: val,
...attrs
},
{ default: () => this.$slots.default() }
);
}
});

View File

@@ -0,0 +1,11 @@
@import "cropperjs/dist/cropper.css";
@import "tippy.js/dist/tippy.css";
@import "tippy.js/themes/light.css";
@import "tippy.js/animations/perspective.css";
.re-circled {
.cropper-view-box,
.cropper-face {
border-radius: 50%;
}
}

View File

@@ -1,22 +1,41 @@
import "./circled.css";
import Cropper from "cropperjs";
import { ElUpload } from "element-plus";
import type { CSSProperties } from "vue";
import { useResizeObserver } from "@vueuse/core";
import { longpress } from "@/directives/longpress";
import { useTippy, directive as tippy } from "vue-tippy";
import { delay, debounce, isArray, downloadByBase64 } from "@pureadmin/utils";
import {
defineComponent,
onMounted,
nextTick,
ref,
unref,
computed,
PropType
PropType,
onMounted,
onUnmounted,
defineComponent
} from "vue";
import { useAttrs } from "@pureadmin/utils";
import Cropper from "cropperjs";
import "cropperjs/dist/cropper.css";
import {
Reload,
Upload,
ArrowH,
ArrowV,
ArrowUp,
ArrowDown,
ArrowLeft,
ChangeIcon,
ArrowRight,
RotateLeft,
SearchPlus,
RotateRight,
SearchMinus,
DownloadIcon
} from "./svg";
type Options = Cropper.Options;
const defaultOptions: Cropper.Options = {
aspectRatio: 16 / 9,
const defaultOptions: Options = {
aspectRatio: 1,
zoomable: true,
zoomOnTouch: true,
zoomOnWheel: true,
@@ -39,110 +58,382 @@ const defaultOptions: Cropper.Options = {
};
const props = {
src: {
type: String,
required: true
},
alt: {
type: String
},
width: {
type: [String, Number],
default: ""
},
height: {
type: [String, Number],
default: "360px"
},
src: { type: String, required: true },
alt: { type: String },
circled: { type: Boolean, default: false },
realTimePreview: { type: Boolean, default: true },
height: { type: [String, Number], default: "360px" },
crossorigin: {
type: String || Object,
type: String as PropType<"" | "anonymous" | "use-credentials" | undefined>,
default: undefined
},
imageStyle: {
type: Object as PropType<CSSProperties>,
default() {
return {};
}
},
options: {
type: Object as PropType<Options>,
default() {
return {};
}
}
imageStyle: { type: Object as PropType<CSSProperties>, default: () => ({}) },
options: { type: Object as PropType<Options>, default: () => ({}) }
};
export default defineComponent({
name: "ReCropper",
props,
setup(props) {
const cropper: any = ref<Nullable<Cropper>>(null);
const imgElRef = ref();
setup(props, { attrs, emit }) {
const tippyElRef = ref<ElRef<HTMLImageElement>>();
const imgElRef = ref<ElRef<HTMLImageElement>>();
const cropper = ref<Nullable<Cropper>>();
const isReady = ref(false);
const imgBase64 = ref();
const inCircled = ref(props.circled);
const inSrc = ref(props.src);
let scaleX = 1;
let scaleY = 1;
const isReady = ref<boolean>(false);
const debounceRealTimeCroppered = debounce(realTimeCroppered, 80);
const getImageStyle = computed((): CSSProperties => {
return {
height: props.height,
width: props.width,
maxWidth: "100%",
...props.imageStyle
};
});
const getWrapperStyle = computed((): CSSProperties => {
const { height, width } = props;
return {
width: `${width}`.replace(/px/, "") + "px",
height: `${height}`.replace(/px/, "") + "px"
};
const getClass = computed(() => {
return [
attrs.class,
{
["re-circled"]: inCircled.value
}
];
});
function init() {
const iconClass = computed(() => {
return [
"p-[6px]",
"h-[30px]",
"w-[30px]",
"outline-none",
"rounded-[4px]",
"cursor-pointer",
"hover:bg-[rgba(0,0,0,0.06)]"
];
});
const getWrapperStyle = computed((): CSSProperties => {
return { height: `${props.height}`.replace(/px/, "") + "px" };
});
onMounted(init);
onUnmounted(() => {
cropper.value?.destroy();
});
useResizeObserver(tippyElRef, () => {
handCropper("reset");
});
async function init() {
const imgEl = unref(imgElRef);
if (!imgEl) {
return;
}
if (!imgEl) return;
cropper.value = new Cropper(imgEl, {
...defaultOptions,
ready: () => {
isReady.value = true;
realTimeCroppered();
delay(400).then(() => emit("readied", cropper.value));
},
crop() {
debounceRealTimeCroppered();
},
zoom() {
debounceRealTimeCroppered();
},
cropmove() {
debounceRealTimeCroppered();
},
...props.options
});
}
onMounted(() => {
nextTick(() => {
init();
function realTimeCroppered() {
props.realTimePreview && croppered();
}
function croppered() {
if (!cropper.value) return;
const canvas = inCircled.value
? getRoundedCanvas()
: cropper.value.getCroppedCanvas();
// https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/toBlob
canvas.toBlob(blob => {
if (!blob) return;
const fileReader: FileReader = new FileReader();
fileReader.readAsDataURL(blob);
fileReader.onloadend = e => {
if (!e.target?.result || !blob) return;
imgBase64.value = e.target.result;
emit("cropper", {
base64: e.target.result,
blob,
info: { size: blob.size, ...cropper.value.getData() }
});
};
fileReader.onerror = () => {
emit("error");
};
});
}
function getRoundedCanvas() {
const sourceCanvas = cropper.value!.getCroppedCanvas();
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d")!;
const width = sourceCanvas.width;
const height = sourceCanvas.height;
canvas.width = width;
canvas.height = height;
context.imageSmoothingEnabled = true;
context.drawImage(sourceCanvas, 0, 0, width, height);
context.globalCompositeOperation = "destination-in";
context.beginPath();
context.arc(
width / 2,
height / 2,
Math.min(width, height) / 2,
0,
2 * Math.PI,
true
);
context.fill();
return canvas;
}
function handCropper(event: string, arg?: number | Array<number>) {
if (event === "scaleX") {
scaleX = arg = scaleX === -1 ? 1 : -1;
}
if (event === "scaleY") {
scaleY = arg = scaleY === -1 ? 1 : -1;
}
arg && isArray(arg)
? cropper.value?.[event]?.(...arg)
: cropper.value?.[event]?.(arg);
}
function beforeUpload(file) {
const reader = new FileReader();
reader.readAsDataURL(file);
inSrc.value = "";
reader.onload = e => {
inSrc.value = e.target?.result as string;
};
reader.onloadend = () => {
init();
};
return false;
}
const menuContent = defineComponent({
directives: {
tippy,
longpress
},
setup() {
return () => (
<div class="flex flex-wrap w-[60px] justify-between">
<ElUpload
accept="image/*"
show-file-list={false}
before-upload={beforeUpload}
>
<Upload
class={iconClass.value}
v-tippy={{
content: "上传",
placement: "left-start"
}}
/>
</ElUpload>
<DownloadIcon
class={iconClass.value}
v-tippy={{
content: "下载",
placement: "right-start"
}}
onClick={() => downloadByBase64(imgBase64.value, "cropping.png")}
/>
<ChangeIcon
class={iconClass.value}
v-tippy={{
content: "圆形、矩形裁剪",
placement: "left-start"
}}
onClick={() => {
inCircled.value = !inCircled.value;
realTimeCroppered();
}}
/>
<Reload
class={iconClass.value}
v-tippy={{
content: "重置",
placement: "right-start"
}}
onClick={() => handCropper("reset")}
/>
<ArrowUp
class={iconClass.value}
v-tippy={{
content: "上移(可长按)",
placement: "left-start"
}}
v-longpress={[() => handCropper("move", [0, -10]), "0:100"]}
/>
<ArrowDown
class={iconClass.value}
v-tippy={{
content: "下移(可长按)",
placement: "right-start"
}}
v-longpress={[() => handCropper("move", [0, 10]), "0:100"]}
/>
<ArrowLeft
class={iconClass.value}
v-tippy={{
content: "左移(可长按)",
placement: "left-start"
}}
v-longpress={[() => handCropper("move", [-10, 0]), "0:100"]}
/>
<ArrowRight
class={iconClass.value}
v-tippy={{
content: "右移(可长按)",
placement: "right-start"
}}
v-longpress={[() => handCropper("move", [10, 0]), "0:100"]}
/>
<ArrowH
class={iconClass.value}
v-tippy={{
content: "水平翻转",
placement: "left-start"
}}
onClick={() => handCropper("scaleX", -1)}
/>
<ArrowV
class={iconClass.value}
v-tippy={{
content: "垂直翻转",
placement: "right-start"
}}
onClick={() => handCropper("scaleY", -1)}
/>
<RotateLeft
class={iconClass.value}
v-tippy={{
content: "逆时针旋转",
placement: "left-start"
}}
onClick={() => handCropper("rotate", -45)}
/>
<RotateRight
class={iconClass.value}
v-tippy={{
content: "顺时针旋转",
placement: "right-start"
}}
onClick={() => handCropper("rotate", 45)}
/>
<SearchPlus
class={iconClass.value}
v-tippy={{
content: "放大(可长按)",
placement: "left-start"
}}
v-longpress={[() => handCropper("zoom", 0.1), "0:100"]}
/>
<SearchMinus
class={iconClass.value}
v-tippy={{
content: "缩小(可长按)",
placement: "right-start"
}}
v-longpress={[() => handCropper("zoom", -0.1), "0:100"]}
/>
</div>
);
}
});
function onContextmenu(event) {
event.preventDefault();
const { show, setProps } = useTippy(tippyElRef, {
content: menuContent,
arrow: false,
theme: "light",
trigger: "manual",
interactive: true,
appendTo: "parent",
// hideOnClick: false,
animation: "perspective",
placement: "bottom-start"
});
setProps({
getReferenceClientRect: () => ({
width: 0,
height: 0,
top: event.clientY,
bottom: event.clientY,
left: event.clientX,
right: event.clientX
})
});
show();
}
return {
inSrc,
props,
imgElRef,
cropper,
tippyElRef,
getClass,
getWrapperStyle,
getImageStyle
getImageStyle,
isReady,
croppered,
onContextmenu
};
},
render() {
return (
<>
<div
class={useAttrs({ excludeListeners: true, excludeKeys: ["class"] })}
style={this.getWrapperStyle}
>
<img
ref="imgElRef"
src={this.props.src}
alt={this.props.alt}
crossorigin={this.props.crossorigin}
style={this.getImageStyle}
/>
</div>
</>
);
const {
inSrc,
isReady,
getClass,
getImageStyle,
onContextmenu,
getWrapperStyle
} = this;
const { alt, crossorigin } = this.props;
return inSrc ? (
<div
ref="tippyElRef"
class={getClass}
style={getWrapperStyle}
onContextmenu={event => onContextmenu(event)}
>
<img
v-show={isReady}
ref="imgElRef"
style={getImageStyle}
src={inSrc}
alt={alt}
crossorigin={crossorigin}
/>
</div>
) : null;
}
});

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M862 465.3h-81c-4.6 0-9 2-12.1 5.5L550 723.1V160c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v563.1L255.1 470.8c-3-3.5-7.4-5.5-12.1-5.5h-81c-6.8 0-10.5 8.1-6 13.2L487.9 861a31.96 31.96 0 0 0 48.3 0L868 478.5c4.5-5.2.8-13.2-6-13.2z"/></svg>

After

Width:  |  Height:  |  Size: 347 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024"><path d="m296.992 216.992-272 272L3.008 512l21.984 23.008 272 272 46.016-46.016L126.016 544h772L680.992 760.992l46.016 46.016 272-272L1020.992 512l-21.984-23.008-272-272-46.048 46.048L898.016 480h-772l216.96-216.992z"/></svg>

After

Width:  |  Height:  |  Size: 325 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M872 474H286.9l350.2-304c5.6-4.9 2.2-14-5.2-14h-88.5c-3.9 0-7.6 1.4-10.5 3.9L155 487.8a31.96 31.96 0 0 0 0 48.3L535.1 866c1.5 1.3 3.3 2 5.2 2h91.5c7.4 0 10.8-9.2 5.2-14L286.9 550H872c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"/></svg>

After

Width:  |  Height:  |  Size: 344 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M869 487.8 491.2 159.9c-2.9-2.5-6.6-3.9-10.5-3.9h-88.5c-7.4 0-10.8 9.2-5.2 14l350.2 304H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h585.1L386.9 854c-5.6 4.9-2.2 14 5.2 14h91.5c1.9 0 3.8-.7 5.2-2L869 536.2a32.07 32.07 0 0 0 0-48.4z"/></svg>

After

Width:  |  Height:  |  Size: 351 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M868 545.5 536.1 163a31.96 31.96 0 0 0-48.3 0L156 545.5a7.97 7.97 0 0 0 6 13.2h81c4.6 0 9-2 12.1-5.5L474 300.9V864c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V300.9l218.9 252.3c3 3.5 7.4 5.5 12.1 5.5h81c6.8 0 10.5-8 6-13.2z"/></svg>

After

Width:  |  Height:  |  Size: 339 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024"><path d="m512 67.008-23.008 21.984-256 256 46.048 46.048L480 190.016v644L279.008 632.96l-46.048 46.08 256 256 23.008 21.984 23.008-21.984 256-256-46.016-46.016L544 834.016v-644l200.992 200.96 46.016-45.984-256-256z"/></svg>

After

Width:  |  Height:  |  Size: 323 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024"><path d="M956.8 988.8H585.6c-16 0-25.6-9.6-25.6-28.8V576c0-16 9.6-28.8 25.6-28.8h371.2c16 0 25.6 9.6 25.6 28.8v384c0 16-9.6 28.8-25.6 28.8zM608 937.6h326.4V598.4H608v339.2zm-121.6 44.8C262.4 982.4 144 848 144 595.2c0-19.2 9.6-28.8 25.6-28.8s25.6 12.8 25.6 28.8c0 220.8 96 326.4 288 326.4 16 0 25.6 12.8 25.6 28.8s-6.4 32-22.4 32z"/><path d="M262.4 694.4c-6.4 0-9.6-3.2-16-6.4L160 601.6c-9.6-9.6-9.6-22.4 0-28.8s22.4-9.6 28.8 0l86.4 86.4c9.6 9.6 9.6 22.4 0 28.8-3.2 3.2-6.4 6.4-12.8 6.4z"/><path d="M86.4 694.4c-6.4 0-9.6-3.2-16-6.4-9.6-9.6-9.6-22.4 0-28.8l86.4-86.4c9.6-9.6 22.4-9.6 28.8 0 9.6 9.6 9.6 22.4 0 28.8L99.2 688c-3.2 3.2-6.4 6.4-12.8 6.4zm790.4-249.6c-16 0-28.8-12.8-28.8-32 0-224-99.2-336-300.8-336-16 0-28.8-12.8-28.8-32s9.6-32 28.8-32c233.6 0 355.2 137.6 355.2 396.8 0 22.4-9.6 35.2-25.6 35.2z"/><path d="M876.8 448c-6.4 0-9.6-3.2-16-6.4l-86.4-86.4c-9.6-9.6-9.6-22.4 0-28.8s22.4-9.6 28.8 0l86.4 86.4c9.6 9.6 9.6 22.4 0 28.8 0 3.2-6.4 6.4-12.8 6.4z"/><path d="M876.8 448c-6.4 0-9.6-3.2-16-6.4-9.6-9.6-9.6-22.4 0-28.8l86.4-86.4c9.6-9.6 22.4-9.6 28.8 0s9.6 22.4 0 28.8l-86.4 86.4c-3.2 3.2-6.4 6.4-12.8 6.4zM288 524.8C156.8 524.8 48 416 48 278.4S156.8 35.2 288 35.2 528 144 528 281.6 419.2 524.8 288 524.8zm-3.2-432c-99.2 0-179.2 83.2-179.2 185.6S185.6 464 284.8 464 464 380.8 464 278.4 384 92.8 284.8 92.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M505.7 661a8 8 0 0 0 12.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"/></svg>

After

Width:  |  Height:  |  Size: 428 B

View File

@@ -0,0 +1,31 @@
import Reload from "./reload.svg?component";
import Upload from "./upload.svg?component";
import ArrowH from "./arrow-h.svg?component";
import ArrowV from "./arrow-v.svg?component";
import ArrowUp from "./arrow-up.svg?component";
import ChangeIcon from "./change.svg?component";
import ArrowDown from "./arrow-down.svg?component";
import ArrowLeft from "./arrow-left.svg?component";
import DownloadIcon from "./download.svg?component";
import ArrowRight from "./arrow-right.svg?component";
import RotateLeft from "./rotate-left.svg?component";
import SearchPlus from "./search-plus.svg?component";
import RotateRight from "./rotate-right.svg?component";
import SearchMinus from "./search-minus.svg?component";
export {
Reload,
Upload,
ArrowH,
ArrowV,
ArrowUp,
ArrowDown,
ArrowLeft,
ChangeIcon,
ArrowRight,
RotateLeft,
SearchPlus,
RotateRight,
SearchMinus,
DownloadIcon
};

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M168 504.2c1-43.7 10-86.1 26.9-126 17.3-41 42.1-77.7 73.7-109.4S337 212.3 378 195c42.4-17.9 87.4-27 133.9-27s91.5 9.1 133.8 27A341.5 341.5 0 0 1 755 268.8c9.9 9.9 19.2 20.4 27.8 31.4l-60.2 47a8 8 0 0 0 3 14.1l175.7 43c5 1.2 9.9-2.6 9.9-7.7l.8-180.9c0-6.7-7.7-10.5-12.9-6.3l-56.4 44.1C765.8 155.1 646.2 92 511.8 92 282.7 92 96.3 275.6 92 503.8a8 8 0 0 0 8 8.2h60c4.4 0 7.9-3.5 8-7.8zm756 7.8h-60c-4.4 0-7.9 3.5-8 7.8-1 43.7-10 86.1-26.9 126-17.3 41-42.1 77.8-73.7 109.4A342.45 342.45 0 0 1 512.1 856a342.24 342.24 0 0 1-243.2-100.8c-9.9-9.9-19.2-20.4-27.8-31.4l60.2-47a8 8 0 0 0-3-14.1l-175.7-43c-5-1.2-9.9 2.6-9.9 7.7l-.7 181c0 6.7 7.7 10.5 12.9 6.3l56.4-44.1C258.2 868.9 377.8 932 512.2 932c229.2 0 415.5-183.7 419.8-411.8a8 8 0 0 0-8-8.2z"/></svg>

After

Width:  |  Height:  |  Size: 865 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M672 418H144c-17.7 0-32 14.3-32 32v414c0 17.7 14.3 32 32 32h528c17.7 0 32-14.3 32-32V450c0-17.7-14.3-32-32-32zm-44 402H188V494h440v326z"/><path fill="currentColor" d="M819.3 328.5c-78.8-100.7-196-153.6-314.6-154.2l-.2-64c0-6.5-7.6-10.1-12.6-6.1l-128 101c-4 3.1-3.9 9.1 0 12.3L492 318.6c5.1 4 12.7.4 12.6-6.1v-63.9c12.9.1 25.9.9 38.8 2.5 42.1 5.2 82.1 18.2 119 38.7 38.1 21.2 71.2 49.7 98.4 84.3 27.1 34.7 46.7 73.7 58.1 115.8 11 40.7 14 82.7 8.9 124.8-.7 5.4-1.4 10.8-2.4 16.1h74.9c14.8-103.6-11.3-213-81-302.3z"/></svg>

After

Width:  |  Height:  |  Size: 636 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M480.5 251.2c13-1.6 25.9-2.4 38.8-2.5v63.9c0 6.5 7.5 10.1 12.6 6.1L660 217.6c4-3.2 4-9.2 0-12.3l-128-101c-5.1-4-12.6-.4-12.6 6.1l-.2 64c-118.6.5-235.8 53.4-314.6 154.2-69.6 89.2-95.7 198.6-81.1 302.4h74.9c-.9-5.3-1.7-10.7-2.4-16.1-5.1-42.1-2.1-84.1 8.9-124.8 11.4-42.2 31-81.1 58.1-115.8 27.2-34.7 60.3-63.2 98.4-84.3 37-20.6 76.9-33.6 119.1-38.8z"/><path fill="currentColor" d="M880 418H352c-17.7 0-32 14.3-32 32v414c0 17.7 14.3 32 32 32h528c17.7 0 32-14.3 32-32V450c0-17.7-14.3-32-32-32zm-44 402H396V494h440v326z"/></svg>

After

Width:  |  Height:  |  Size: 639 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M637 443H325c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h312c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8zm284 424L775 721c122.1-148.9 113.6-369.5-26-509-148-148.1-388.4-148.1-537 0-148.1 148.6-148.1 389 0 537 139.5 139.6 360.1 148.1 509 26l146 146c3.2 2.8 8.3 2.8 11 0l43-43c2.8-2.7 2.8-7.8 0-11zM696 696c-118.8 118.7-311.2 118.7-430 0-118.7-118.8-118.7-311.2 0-430 118.8-118.7 311.2-118.7 430 0 118.7 118.8 118.7 311.2 0 430z"/></svg>

After

Width:  |  Height:  |  Size: 535 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M637 443H519V309c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v134H325c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h118v134c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V519h118c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8zm284 424L775 721c122.1-148.9 113.6-369.5-26-509-148-148.1-388.4-148.1-537 0-148.1 148.6-148.1 389 0 537 139.5 139.6 360.1 148.1 509 26l146 146c3.2 2.8 8.3 2.8 11 0l43-43c2.8-2.7 2.8-7.8 0-11zM696 696c-118.8 118.7-311.2 118.7-430 0-118.7-118.8-118.7-311.2 0-430 118.8-118.7 311.2-118.7 430 0 118.7 118.8 118.7 311.2 0 430z"/></svg>

After

Width:  |  Height:  |  Size: 631 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 1024 1024"><path fill="currentColor" d="M400 317.7h73.9V656c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V317.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 163a8 8 0 0 0-12.6 0l-112 141.7c-4.1 5.3-.4 13 6.3 13zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"/></svg>

After

Width:  |  Height:  |  Size: 423 B

View File

@@ -12,6 +12,7 @@ import type {
const dialogStore = ref<Array<DialogOptions>>([]);
/** 打开弹框 */
const addDialog = (options: DialogOptions) => {
const open = () =>
dialogStore.value.push(Object.assign(options, { visible: true }));
@@ -24,16 +25,40 @@ const addDialog = (options: DialogOptions) => {
}
};
/** 关闭弹框 */
const closeDialog = (options: DialogOptions, index: number, args?: any) => {
dialogStore.value.splice(index, 1);
options.closeCallBack && options.closeCallBack({ options, index, args });
};
/**
* @description 更改弹框自身属性值
* @param value 属性值
* @param key 属性,默认`title`
* @param index 弹框索引(默认`0`,代表只有一个弹框,对于嵌套弹框要改哪个弹框的属性值就把该弹框索引赋给`index`
*/
const updateDialog = (value: any, key = "title", index = 0) => {
dialogStore.value[index][key] = value;
};
/** 关闭所有弹框 */
const closeAllDialog = () => {
dialogStore.value = [];
};
/** 千万别忘了在下面这三处引入并注册下,放心注册,不使用`addDialog`调用就不会被挂载
* https://github.com/pure-admin/vue-pure-admin/blob/main/src/App.vue#L4
* https://github.com/pure-admin/vue-pure-admin/blob/main/src/App.vue#L13
* https://github.com/pure-admin/vue-pure-admin/blob/main/src/App.vue#L18
*/
const ReDialog = withInstall(reDialog);
export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions };
export { ReDialog, dialogStore, addDialog, closeDialog, closeAllDialog };
export {
ReDialog,
dialogStore,
addDialog,
closeDialog,
updateDialog,
closeAllDialog
};

View File

@@ -1,13 +1,17 @@
<script setup lang="ts">
import { computed } from "vue";
import { isFunction } from "@pureadmin/utils";
import {
type DialogOptions,
type ButtonProps,
type EventType,
closeDialog,
dialogStore,
closeDialog
type EventType,
type ButtonProps,
type DialogOptions
} from "./index";
import { ref, computed } from "vue";
import { isFunction } from "@pureadmin/utils";
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
const fullscreen = ref(false);
const footerButtons = computed(() => {
return (options: DialogOptions) => {
@@ -19,7 +23,13 @@ const footerButtons = computed(() => {
text: true,
bg: true,
btnClick: ({ dialog: { options, index } }) => {
closeDialog(options, index, { command: "cancel" });
const done = () =>
closeDialog(options, index, { command: "cancel" });
if (options?.beforeCancel && isFunction(options?.beforeCancel)) {
options.beforeCancel(done, { options, index });
} else {
done();
}
}
},
{
@@ -28,18 +38,35 @@ const footerButtons = computed(() => {
text: true,
bg: true,
btnClick: ({ dialog: { options, index } }) => {
closeDialog(options, index, { command: "sure" });
const done = () =>
closeDialog(options, index, { command: "sure" });
if (options?.beforeSure && isFunction(options?.beforeSure)) {
options.beforeSure(done, { options, index });
} else {
done();
}
}
}
] as Array<ButtonProps>);
};
});
const fullscreenClass = computed(() => {
return [
"el-icon",
"el-dialog__close",
"-translate-x-2",
"cursor-pointer",
"hover:!text-[red]"
];
});
function eventsCallBack(
event: EventType,
options: DialogOptions,
index: number
) {
fullscreen.value = options?.fullscreen ?? false;
if (options?.[event] && isFunction(options?.[event])) {
return options?.[event]({ options, index });
}
@@ -57,25 +84,49 @@ function handleClose(
<template>
<el-dialog
class="pure-dialog"
v-for="(options, index) in dialogStore"
:key="index"
v-bind="options"
v-model="options.visible"
@opened="eventsCallBack('open', options, index)"
:fullscreen="fullscreen ? true : options?.fullscreen ? true : false"
@close="handleClose(options, index)"
@opened="eventsCallBack('open', options, index)"
@openAutoFocus="eventsCallBack('openAutoFocus', options, index)"
@closeAutoFocus="eventsCallBack('closeAutoFocus', options, index)"
>
<!-- header -->
<template
v-if="options?.headerRenderer"
v-if="options?.fullscreenIcon || options?.headerRenderer"
#header="{ close, titleId, titleClass }"
>
<div
v-if="options?.fullscreenIcon"
class="flex items-center justify-between"
>
<span :id="titleId" :class="titleClass">{{ options?.title }}</span>
<i
v-if="!options?.fullscreen"
:class="fullscreenClass"
@click="fullscreen = !fullscreen"
>
<IconifyIconOffline
class="pure-dialog-svg"
:icon="
options?.fullscreen
? ExitFullscreen
: fullscreen
? ExitFullscreen
: Fullscreen
"
/>
</i>
</div>
<component
v-else
:is="options?.headerRenderer({ close, titleId, titleClass })"
/>
</template>
<!-- default -->
<component
v-bind="options?.props"
:is="options.contentRenderer({ options, index })"

View File

@@ -15,8 +15,10 @@ type DialogProps = {
title?: string;
/** `Dialog` 的宽度,默认 `50%` */
width?: string | number;
/** 是否为全屏 `Dialog`,默认 `false` */
/** 是否为全屏 `Dialog`(会一直处于全屏状态,除非弹框关闭),默认 `false``fullscreen` 和 `fullscreenIcon` 都传时只有 `fullscreen` 会生效 */
fullscreen?: boolean;
/** 是否显示全屏操作图标,默认 `false``fullscreen` 和 `fullscreenIcon` 都传时只有 `fullscreen` 会生效 */
fullscreenIcon?: boolean;
/** `Dialog CSS` 中的 `margin-top` 值,默认 `15vh` */
top?: string;
/** 是否需要遮罩层,默认 `true` */
@@ -128,7 +130,7 @@ interface DialogOptions extends DialogProps {
close: Function;
titleId: string;
titleClass: string;
}) => VNode;
}) => VNode | Component;
/** 自定义内容渲染器 */
contentRenderer?: ({
options,
@@ -136,7 +138,7 @@ interface DialogOptions extends DialogProps {
}: {
options: DialogOptions;
index: number;
}) => VNode;
}) => VNode | Component;
/** 自定义按钮操作区的内容渲染器,会覆盖`footerButtons`以及默认的 `取消` 和 `确定` 按钮 */
footerRenderer?: ({
options,
@@ -144,7 +146,7 @@ interface DialogOptions extends DialogProps {
}: {
options: DialogOptions;
index: number;
}) => VNode;
}) => VNode | Component;
/** 自定义底部按钮操作 */
footerButtons?: Array<ButtonProps>;
/** `Dialog` 打开后的回调 */
@@ -189,6 +191,28 @@ interface DialogOptions extends DialogProps {
options: DialogOptions;
index: number;
}) => void;
/** 点击底部取消按钮的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */
beforeCancel?: (
done: Function,
{
options,
index
}: {
options: DialogOptions;
index: number;
}
) => void;
/** 点击底部确定按钮的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */
beforeSure?: (
done: Function,
{
options,
index
}: {
options: DialogOptions;
index: number;
}
) => void;
}
export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions };

View File

@@ -105,7 +105,8 @@ watch(
props.modelValue.indexOf(":") + 1
);
}
}
},
{ immediate: true }
);
watch(
() => {
@@ -140,12 +141,11 @@ watch(
</template>
<el-input
class="p-2"
class="px-2 pt-2"
v-model="filterValue"
placeholder="搜索图标"
clearable
/>
<el-divider border-style="dashed" />
<el-tabs v-model="currentActiveType" @tab-click="handleClick">
<el-tab-pane
@@ -154,24 +154,26 @@ watch(
:label="pane.label"
:name="pane.name"
>
<el-divider class="tab-divider" border-style="dashed" />
<el-scrollbar height="220px">
<ul class="flex flex-wrap px-2 ml-2">
<li
v-for="(item, key) in pageList"
:key="key"
:title="item"
class="icon-item p-2 w-[1/10] cursor-pointer mr-2 mt-1 flex justify-center items-center border border-solid"
class="icon-item p-2 cursor-pointer mr-2 mt-1 flex justify-center items-center border border-solid"
:style="iconItemStyle(item)"
@click="onChangeIcon(item)"
>
<IconifyIconOnline :icon="currentActiveType + item" />
<IconifyIconOnline
:icon="currentActiveType + item"
width="20px"
height="20px"
/>
</li>
</ul>
</el-scrollbar>
</el-tab-pane>
</el-tabs>
<el-divider border-style="dashed" />
<el-pagination
small
@@ -190,14 +192,6 @@ watch(
</template>
<style lang="scss" scoped>
.el-divider--horizontal {
margin: 1px auto !important;
}
.tab-divider.el-divider--horizontal {
margin: 0 !important;
}
.icon-item {
&:hover {
color: var(--el-color-primary);
@@ -234,5 +228,10 @@ watch(
:deep(.el-tabs__nav-wrap) {
position: static;
margin: 0;
box-shadow: 0 2px 5px rgb(0 0 0 / 6%);
}
:deep(.el-tabs__content) {
margin-top: 4px;
}
</style>

View File

@@ -1,6 +1,15 @@
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
import { delay, getKeyList, cloneDeep } from "@pureadmin/utils";
import { defineComponent, ref, computed, type PropType } from "vue";
import { defineComponent, ref, computed, type PropType, nextTick } from "vue";
import {
delay,
cloneDeep,
isBoolean,
isFunction,
getKeyList
} from "@pureadmin/utils";
import Sortable from "sortablejs";
import DragIcon from "./svg/drag.svg?component";
import ExpandIcon from "./svg/expand.svg?component";
import RefreshIcon from "./svg/refresh.svg?component";
import SettingIcon from "./svg/settings.svg?component";
@@ -34,8 +43,13 @@ export default defineComponent({
const loading = ref(false);
const checkAll = ref(true);
const isIndeterminate = ref(false);
const filterColumns = cloneDeep(props?.columns).filter(column =>
isBoolean(column?.hide)
? !column.hide
: !(isFunction(column?.hide) && column?.hide())
);
let checkColumnList = getKeyList(cloneDeep(props?.columns), "label");
const checkedColumns = ref(checkColumnList);
const checkedColumns = ref(getKeyList(cloneDeep(filterColumns), "label"));
const dynamicColumns = ref(cloneDeep(props?.columns));
const getDropdownItemStyle = computed(() => {
@@ -107,16 +121,17 @@ export default defineComponent({
checkedCount > 0 && checkedCount < checkColumnList.length;
}
function handleCheckColumnListChange(val: boolean, index: number) {
dynamicColumns.value[index].hide = !val;
function handleCheckColumnListChange(val: boolean, label: string) {
dynamicColumns.value.filter(item => item.label === label)[0].hide = !val;
}
function onReset() {
async function onReset() {
checkAll.value = true;
isIndeterminate.value = false;
checkColumnList = getKeyList(cloneDeep(props?.columns), "label");
checkedColumns.value = checkColumnList;
dynamicColumns.value = cloneDeep(props?.columns);
checkColumnList = [];
checkColumnList = await getKeyList(cloneDeep(props?.columns), "label");
checkedColumns.value = getKeyList(cloneDeep(filterColumns), "label");
}
const dropdown = {
@@ -144,6 +159,47 @@ export default defineComponent({
)
};
/** 列展示拖拽排序 */
const rowDrop = (event: { preventDefault: () => void }) => {
event.preventDefault();
nextTick(() => {
const wrapper: HTMLElement = document.querySelector(
".el-checkbox-group>div"
);
Sortable.create(wrapper, {
animation: 300,
handle: ".drag-btn",
onEnd: ({ newIndex, oldIndex, item }) => {
const targetThElem = item;
const wrapperElem = targetThElem.parentNode as HTMLElement;
const oldColumn = dynamicColumns.value[oldIndex];
const newColumn = dynamicColumns.value[newIndex];
if (oldColumn?.fixed || newColumn?.fixed) {
// 当前列存在fixed属性 则不可拖拽
const oldThElem = wrapperElem.children[oldIndex] as HTMLElement;
if (newIndex > oldIndex) {
wrapperElem.insertBefore(targetThElem, oldThElem);
} else {
wrapperElem.insertBefore(
targetThElem,
oldThElem ? oldThElem.nextElementSibling : oldThElem
);
}
return;
}
const currentRow = dynamicColumns.value.splice(oldIndex, 1)[0];
dynamicColumns.value.splice(newIndex, 0, currentRow);
}
});
});
};
const isFixedColumn = (label: string) => {
return dynamicColumns.value.filter(item => item.label === label)[0].fixed
? true
: false;
};
const reference = {
reference: () => (
<SettingIcon
@@ -155,9 +211,13 @@ export default defineComponent({
return () => (
<>
<div {...attrs} class="w-[99/100] mt-6 p-2 bg-bg_color">
<div {...attrs} class="w-[99/100] mt-2 px-2 pb-2 bg-bg_color">
<div class="flex justify-between w-full h-[60px] p-4">
<p class="font-bold truncate">{props.title}</p>
{slots?.title ? (
slots.title()
) : (
<p class="font-bold truncate">{props.title}</p>
)}
<div class="flex items-center justify-around">
{slots?.buttons ? (
<div class="flex mr-4">{slots.buttons()}</div>
@@ -191,7 +251,6 @@ export default defineComponent({
/>
</el-tooltip>
<el-divider direction="vertical" />
<el-tooltip effect="dark" content="密度" placement="top">
<el-dropdown v-slots={dropdown} trigger="click">
<CollapseIcon class={["w-[16px]", iconClass.value]} />
@@ -201,6 +260,7 @@ export default defineComponent({
<el-popover
v-slots={reference}
placement="bottom-start"
popper-style={{ padding: 0 }}
width="160"
trigger="click"
@@ -228,22 +288,35 @@ export default defineComponent({
alignment="flex-start"
size={0}
>
{checkColumnList.map((item, index) => {
{checkColumnList.map(item => {
return (
<el-checkbox
key={item}
label={item}
onChange={value =>
handleCheckColumnListChange(value, index)
}
>
<span
title={item}
class="inline-block w-[120px] truncate hover:text-text_color_primary"
<div class="flex items-center">
<DragIcon
class={[
"drag-btn w-[16px] mr-2",
isFixedColumn(item)
? "!cursor-no-drop"
: "!cursor-grab"
]}
onMouseenter={(event: {
preventDefault: () => void;
}) => rowDrop(event)}
/>
<el-checkbox
key={item}
label={item}
onChange={value =>
handleCheckColumnListChange(value, item)
}
>
{item}
</span>
</el-checkbox>
<span
title={item}
class="inline-block w-[120px] truncate hover:text-text_color_primary"
>
{item}
</span>
</el-checkbox>
</div>
);
})}
</el-space>

View File

@@ -0,0 +1 @@
<svg width="32" height="32" fill="currentColor" aria-hidden="true" data-icon="holder" viewBox="64 64 896 896"><path d="M300 276.5a56 56 0 1 0 56-97 56 56 0 0 0-56 97zm0 284a56 56 0 1 0 56-97 56 56 0 0 0-56 97zM640 228a56 56 0 1 0 112 0 56 56 0 0 0-112 0zm0 284a56 56 0 1 0 112 0 56 56 0 0 0-112 0zM300 844.5a56 56 0 1 0 56-97 56 56 0 0 0-56 97zM640 796a56 56 0 1 0 112 0 56 56 0 0 0-112 0z"/></svg>

After

Width:  |  Height:  |  Size: 398 B

View File

@@ -51,20 +51,21 @@ let startPosX = null;
let isHover = false;
let ease = "ease-in";
// eslint-disable-next-line vue/no-setup-props-destructure
const { classOption } = props;
if (classOption["key"] === undefined) {
classOption["key"] = 0;
if (props.classOption["key"] === undefined) {
// eslint-disable-next-line vue/no-mutating-props
props.classOption["key"] = 0;
}
const wrap = templateRef<HTMLElement | null>(`wrap${classOption["key"]}`, null);
const wrap = templateRef<HTMLElement | null>(
`wrap${props.classOption["key"]}`,
null
);
const slotList = templateRef<HTMLElement | null>(
`slotList${classOption["key"]}`,
`slotList${props.classOption["key"]}`,
null
);
const realBox = templateRef<HTMLElement | null>(
`realBox${classOption["key"]}`,
`realBox${props.classOption["key"]}`,
null
);
@@ -107,7 +108,7 @@ const defaultOption = computed(() => {
const options = computed(() => {
// @ts-expect-error
return copyObj({}, unref(defaultOption), classOption);
return copyObj({}, unref(defaultOption), props.classOption);
});
const leftSwitchClass = computed(() => {
@@ -495,7 +496,7 @@ defineExpose({
</script>
<template>
<div :ref="'wrap' + classOption['key']">
<div :ref="'wrap' + props.classOption['key']">
<div
:style="leftSwitch"
v-if="navigation"
@@ -513,7 +514,7 @@ defineExpose({
<slot name="right-switch" />
</div>
<div
:ref="'realBox' + classOption['key']"
:ref="'realBox' + props.classOption['key']"
:style="pos"
@mouseenter="enter"
@mouseleave="leave"
@@ -522,7 +523,7 @@ defineExpose({
@touchend="touchEnd"
@mousewheel.passive="wheel"
>
<div :ref="'slotList' + classOption['key']" :style="float">
<div :ref="'slotList' + props.classOption['key']" :style="float">
<slot />
</div>
<div v-html="copyHtml" :style="float" />

View File

@@ -0,0 +1,8 @@
import reSegmented from "./src/index";
import { withInstall } from "@pureadmin/utils";
/** 分段控制器组件 */
export const ReSegmented = withInstall(reSegmented);
export default ReSegmented;
export type { OptionsType } from "./src/type";

View File

@@ -0,0 +1,78 @@
.pure-segmented {
box-sizing: border-box;
display: inline-block;
padding: 2px;
font-size: 14px;
color: rgba(0, 0, 0, 0.65);
background-color: rgb(0 0 0 / 4%);
border-radius: 2px;
transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
}
.pure-segmented-group {
position: relative;
display: flex;
align-items: stretch;
justify-items: flex-start;
width: 100%;
}
.pure-segmented-item-selected {
position: absolute;
top: 0;
left: 0;
box-sizing: border-box;
display: none;
width: 0;
height: 100%;
padding: 4px 0;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 8px -2px rgb(0 0 0 / 5%), 0 1px 4px -1px rgb(0 0 0 / 7%),
0 0 1px rgb(0 0 0 / 7%);
transition: transform 0.5s cubic-bezier(0.645, 0.045, 0.355, 1),
width 0.5s cubic-bezier(0.645, 0.045, 0.355, 1);
will-change: transform, width;
}
.pure-segmented-item {
position: relative;
text-align: center;
cursor: pointer;
border-radius: 4px;
transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
}
.pure-segmented-item > div {
min-height: 28px;
line-height: 28px;
padding: 0 11px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.pure-segmented-item > input {
position: absolute;
inset-block-start: 0;
inset-inline-start: 0;
width: 0;
height: 0;
opacity: 0;
pointer-events: none;
}
.pure-segmented-item-label {
display: flex;
align-items: center;
}
.pure-segmented-item-icon svg {
width: 16px;
height: 16px;
}
.pure-segmented-item-disabled {
color: rgba(0, 0, 0, 0.25);
cursor: not-allowed;
}

View File

@@ -0,0 +1,150 @@
import "./index.css";
import {
h,
ref,
watch,
nextTick,
defineComponent,
getCurrentInstance
} from "vue";
import type { OptionsType } from "./type";
import { isFunction, useDark } from "@pureadmin/utils";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
const props = {
options: {
type: Array<OptionsType>,
default: () => []
},
/** 默认选中,按照第一个索引为 `0` 的模式 */
defaultValue: {
type: Number,
default: 0
}
};
export default defineComponent({
name: "ReSegmented",
props,
emits: ["change"],
setup(props, { emit }) {
const width = ref(0);
const translateX = ref(0);
const { isDark } = useDark();
const initStatus = ref(false);
const curMouseActive = ref(-1);
const segmentedItembg = ref("");
const instance = getCurrentInstance()!;
const curIndex = ref(props.defaultValue);
function handleChange({ option, index }, event: Event) {
if (option.disabled) return;
event.preventDefault();
curIndex.value = index;
segmentedItembg.value = "";
emit("change", { index, option });
}
function handleMouseenter({ option, index }, event: Event) {
event.preventDefault();
curMouseActive.value = index;
if (option.disabled || curIndex.value === index) {
segmentedItembg.value = "";
} else {
segmentedItembg.value = isDark.value
? "#1f1f1f"
: "rgba(0, 0, 0, 0.06)";
}
}
function handleMouseleave(_, event: Event) {
event.preventDefault();
curMouseActive.value = -1;
}
function handleInit(index = curIndex.value) {
nextTick(() => {
const curLabelRef = instance?.proxy?.$refs[`labelRef${index}`] as ElRef;
width.value = curLabelRef.clientWidth;
translateX.value = curLabelRef.offsetLeft;
initStatus.value = true;
});
}
watch(
() => curIndex.value,
index => {
nextTick(() => {
handleInit(index);
});
},
{
deep: true,
immediate: true
}
);
const rendLabel = () => {
return props.options.map((option, index) => {
return (
<label
ref={`labelRef${index}`}
class={[
"pure-segmented-item",
option?.disabled && "pure-segmented-item-disabled"
]}
style={{
background:
curMouseActive.value === index ? segmentedItembg.value : "",
color:
!option.disabled &&
(curIndex.value === index || curMouseActive.value === index)
? isDark.value
? "rgba(255, 255, 255, 0.85)"
: "rgba(0,0,0,.88)"
: ""
}}
onMouseenter={event => handleMouseenter({ option, index }, event)}
onMouseleave={event => handleMouseleave({ option, index }, event)}
onClick={event => handleChange({ option, index }, event)}
>
<input type="radio" name="segmented" />
<div class="pure-segmented-item-label">
{option.icon && !isFunction(option.label) ? (
<span
class="pure-segmented-item-icon"
style={{ marginRight: option.label ? "6px" : 0 }}
>
{h(useRenderIcon(option.icon))}
</span>
) : null}
{option.label ? (
isFunction(option.label) ? (
h(option.label)
) : (
<span>{option.label}</span>
)
) : null}
</div>
</label>
);
});
};
return () => (
<div class="pure-segmented">
<div class="pure-segmented-group">
<div
class="pure-segmented-item-selected"
style={{
width: `${width.value}px`,
transform: `translateX(${translateX.value}px)`,
display: initStatus.value ? "block" : "none"
}}
></div>
{rendLabel()}
</div>
</div>
);
}
});

View File

@@ -0,0 +1,15 @@
import type { VNode, Component } from "vue";
export interface OptionsType {
/** 文字 */
label?: string | (() => VNode | Component);
/**
* @description 图标,采用平台内置的 `useRenderIcon` 函数渲染
* @see {@link 用法参考 https://yiming_chang.gitee.io/pure-admin-doc/pages/icon/#%E9%80%9A%E7%94%A8%E5%9B%BE%E6%A0%87-userendericon-hooks }
*/
icon?: string | Component;
/** 值 */
value?: string | number;
/** 是否禁用 */
disabled?: boolean;
}

View File

@@ -1,5 +1,5 @@
import { hasAuth } from "@/router/utils";
import { Directive, type DirectiveBinding } from "vue";
import type { Directive, DirectiveBinding } from "vue";
export const auth: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
@@ -7,7 +7,9 @@ export const auth: Directive = {
if (value) {
!hasAuth(value) && el.parentNode?.removeChild(el);
} else {
throw new Error("need auths! Like v-auth=\"['btn.add','btn.edit']\"");
throw new Error(
"[Directive: auth]: need auths! Like v-auth=\"['btn.add','btn.edit']\""
);
}
}
};

View File

@@ -0,0 +1,33 @@
import { message } from "@/utils/message";
import { useEventListener } from "@vueuse/core";
import { copyTextToClipboard } from "@pureadmin/utils";
import type { Directive, DirectiveBinding } from "vue";
interface CopyEl extends HTMLElement {
copyValue: string;
}
/** 文本复制指令(默认双击复制) */
export const copy: Directive = {
mounted(el: CopyEl, binding: DirectiveBinding) {
const { value } = binding;
if (value) {
el.copyValue = value;
const arg = binding.arg ?? "dblclick";
// Register using addEventListener on mounted, and removeEventListener automatically on unmounted
useEventListener(el, arg, () => {
const success = copyTextToClipboard(el.copyValue);
success
? message("复制成功", { type: "success" })
: message("复制失败", { type: "error" });
});
} else {
throw new Error(
'[Directive: copy]: need value! Like v-copy="modelValue"'
);
}
},
updated(el: CopyEl, binding: DirectiveBinding) {
el.copyValue = binding.value;
}
};

View File

@@ -1,27 +0,0 @@
import { Directive, type DirectiveBinding, type VNode } from "vue";
import elementResizeDetectorMaker from "element-resize-detector";
import type { Erd } from "element-resize-detector";
import { emitter } from "@/utils/mitt";
const erd: Erd = elementResizeDetectorMaker({
strategy: "scroll"
});
export const resize: Directive = {
mounted(el: HTMLElement, binding?: DirectiveBinding, vnode?: VNode) {
erd.listenTo(el, elem => {
const width = elem.offsetWidth;
const height = elem.offsetHeight;
if (binding?.instance) {
emitter.emit("resize", { detail: { width, height } });
} else {
vnode.el.dispatchEvent(
new CustomEvent("resize", { detail: { width, height } })
);
}
});
},
unmounted(el: HTMLElement) {
erd.uninstall(el);
}
};

View File

@@ -1,2 +1,4 @@
export * from "./auth";
export * from "./elResizeDetector";
export * from "./copy";
export * from "./longpress";
export * from "./optimize";

View File

@@ -0,0 +1,63 @@
import { useEventListener } from "@vueuse/core";
import type { Directive, DirectiveBinding } from "vue";
import { subBefore, subAfter, isFunction } from "@pureadmin/utils";
export const longpress: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const cb = binding.value;
if (cb && isFunction(cb)) {
let timer = null;
let interTimer = null;
let num = 500;
let interNum = null;
const isInter = binding?.arg?.includes(":") ?? false;
if (isInter) {
num = Number(subBefore(binding.arg, ":"));
interNum = Number(subAfter(binding.arg, ":"));
} else if (binding.arg) {
num = Number(binding.arg);
}
const clear = () => {
if (timer) {
clearTimeout(timer);
timer = null;
}
if (interTimer) {
clearInterval(interTimer);
interTimer = null;
}
};
const onDownInter = (ev: PointerEvent) => {
ev.preventDefault();
if (interTimer === null) {
interTimer = setInterval(() => cb(), interNum);
}
};
const onDown = (ev: PointerEvent) => {
clear();
ev.preventDefault();
if (timer === null) {
timer = isInter
? setTimeout(() => {
cb();
onDownInter(ev);
}, num)
: setTimeout(() => cb(), num);
}
};
// Register using addEventListener on mounted, and removeEventListener automatically on unmounted
useEventListener(el, "pointerdown", onDown);
useEventListener(el, "pointerup", clear);
useEventListener(el, "pointerleave", clear);
} else {
throw new Error(
'[Directive: longpress]: need callback and callback must be a function! Like v-longpress="callback"'
);
}
}
};

View File

@@ -0,0 +1,55 @@
import {
isFunction,
isObject,
isArray,
debounce,
throttle
} from "@pureadmin/utils";
import { useEventListener } from "@vueuse/core";
import type { Directive, DirectiveBinding } from "vue";
/** 防抖v-optimize或v-optimize:debounce、节流v-optimize:throttle指令 */
export const optimize: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const { value } = binding;
const optimizeType = binding.arg ?? "debounce";
const type = ["debounce", "throttle"].find(t => t === optimizeType);
if (type) {
if (value && value.event && isFunction(value.fn)) {
let params = value?.params;
if (params) {
if (isArray(params) || isObject(params)) {
params = isObject(params) ? Array.of(params) : params;
} else {
throw new Error(
"[Directive: optimize]: `params` must be an array or object"
);
}
}
// Register using addEventListener on mounted, and removeEventListener automatically on unmounted
useEventListener(
el,
value.event,
type === "debounce"
? debounce(
params ? () => value.fn(...params) : value.fn,
value?.timeout ?? 200,
value?.immediate ?? false
)
: throttle(
params ? () => value.fn(...params) : value.fn,
value?.timeout ?? 1000
)
);
} else {
throw new Error(
"[Directive: optimize]: `event` and `fn` are required, and `fn` must be a function"
);
}
} else {
throw new Error(
"[Directive: optimize]: only `debounce` and `throttle` are supported"
);
}
}
};

View File

@@ -18,6 +18,7 @@ const {
onPanel,
pureApp,
username,
userAvatar,
avatarsStyle,
toggleSideBar,
getDropdownItemStyle,
@@ -85,10 +86,7 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
<!-- 退出登录 -->
<el-dropdown trigger="click">
<span class="el-dropdown-link navbar-bg-hover select-none">
<img
src="https://avatars.githubusercontent.com/u/44761321?v=4"
:style="avatarsStyle"
/>
<img :src="userAvatar" :style="avatarsStyle" />
<p v-if="username" class="dark:text-white">{{ username }}</p>
</span>
<template #dropdown>

View File

@@ -22,19 +22,31 @@ notices.value.map(v => (noticesNum.value += v.list.length));
</span>
<template #dropdown>
<el-dropdown-menu>
<el-tabs :stretch="true" v-model="activeKey" class="dropdown-tabs">
<template v-for="item in notices" :key="item.key">
<el-tab-pane
:label="`${item.name}(${item.list.length})`"
:name="`${item.key}`"
>
<el-scrollbar max-height="330px">
<div class="noticeList-container">
<NoticeList :list="item.list" />
</div>
</el-scrollbar>
</el-tab-pane>
</template>
<el-tabs
:stretch="true"
v-model="activeKey"
class="dropdown-tabs"
:style="{ width: notices.length === 0 ? '200px' : '330px' }"
>
<el-empty
v-if="notices.length === 0"
description="暂无消息"
:image-size="60"
/>
<span v-else>
<template v-for="item in notices" :key="item.key">
<el-tab-pane
:label="`${item.name}(${item.list.length})`"
:name="`${item.key}`"
>
<el-scrollbar max-height="330px">
<div class="noticeList-container">
<NoticeList :list="item.list" />
</div>
</el-scrollbar>
</el-tab-pane>
</template>
</span>
</el-tabs>
</el-dropdown-menu>
</template>
@@ -46,8 +58,9 @@ notices.value.map(v => (noticesNum.value += v.list.length));
display: flex;
align-items: center;
justify-content: center;
width: 60px;
width: 40px;
height: 48px;
margin-right: 10px;
cursor: pointer;
.header-notice-icon {
@@ -56,8 +69,6 @@ notices.value.map(v => (noticesNum.value += v.list.length));
}
.dropdown-tabs {
width: 330px;
.noticeList-container {
padding: 15px 24px 0;
}

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref, computed } from "vue";
import { emitter } from "@/utils/mitt";
import { onClickOutside } from "@vueuse/core";
import { ref, computed, onMounted, onBeforeUnmount } from "vue";
import Close from "@iconify-icons/ep/close";
const target = ref(null);
@@ -27,8 +27,15 @@ onClickOutside(target, (event: any) => {
show.value = false;
});
emitter.on("openPanel", () => {
show.value = true;
onMounted(() => {
emitter.on("openPanel", () => {
show.value = true;
});
});
onBeforeUnmount(() => {
// 解绑`openPanel`公共事件,防止多次触发
emitter.off("openPanel");
});
</script>

View File

@@ -1,3 +1,17 @@
<script setup lang="ts">
import ArrowUpLine from "@iconify-icons/ri/arrow-up-line";
import ArrowDownLine from "@iconify-icons/ri/arrow-down-line";
import { useNav } from "@/layout/hooks/useNav";
import mdiKeyboardEsc from "@/assets/svg/keyboard_esc.svg?component";
import enterOutlined from "@/assets/svg/enter_outlined.svg?component";
const props = withDefaults(defineProps<{ total: number }>(), {
total: 0
});
const { device } = useNav();
</script>
<template>
<div class="search-footer text-[#333] dark:text-white">
<span class="search-footer-item">
@@ -13,16 +27,15 @@
<mdiKeyboardEsc class="icon" />
关闭
</span>
<p
v-if="device !== 'mobile' && props.total > 0"
class="search-footer-total"
>
{{ props.total }}
</p>
</div>
</template>
<script setup lang="ts">
import ArrowUpLine from "@iconify-icons/ri/arrow-up-line";
import ArrowDownLine from "@iconify-icons/ri/arrow-down-line";
import mdiKeyboardEsc from "@/assets/svg/keyboard_esc.svg?component";
import enterOutlined from "@/assets/svg/enter_outlined.svg?component";
</script>
<style lang="scss" scoped>
.search-footer {
display: flex;
@@ -40,5 +53,10 @@ import enterOutlined from "@/assets/svg/enter_outlined.svg?component";
box-shadow: inset 0 -2px #cdcde6, inset 0 0 1px 1px #fff,
0 1px 2px 1px #1e235a66;
}
.search-footer-total {
position: absolute;
right: 20px;
}
}
</style>

View File

@@ -1,14 +1,16 @@
<script setup lang="ts">
import { match } from "pinyin-pro";
import { useI18n } from "vue-i18n";
import { useRouter } from "vue-router";
import { cloneDeep } from "@pureadmin/utils";
import SearchResult from "./SearchResult.vue";
import SearchFooter from "./SearchFooter.vue";
import { useNav } from "@/layout/hooks/useNav";
import { transformI18n } from "@/plugins/i18n";
import { ref, computed, shallowRef } from "vue";
import { cloneDeep, isAllEmpty } from "@pureadmin/utils";
import { useDebounceFn, onKeyStroke } from "@vueuse/core";
import { usePermissionStoreHook } from "@/store/modules/permission";
import Search from "@iconify-icons/ep/search";
import Search from "@iconify-icons/ri/search-line";
interface Props {
/** 弹窗显隐 */
@@ -23,8 +25,11 @@ const { device } = useNav();
const emit = defineEmits<Emits>();
const props = withDefaults(defineProps<Props>(), {});
const router = useRouter();
const { locale } = useI18n();
const keyword = ref("");
const scrollbarRef = ref();
const resultRef = ref();
const activePath = ref("");
const inputRef = ref<HTMLInputElement | null>(null);
const resultOptions = shallowRef([]);
@@ -60,12 +65,19 @@ function flatTree(arr) {
/** 查询 */
function search() {
const flatMenusData = flatTree(menusData.value);
resultOptions.value = flatMenusData.filter(
menu =>
keyword.value &&
transformI18n(menu.meta?.title)
.toLocaleLowerCase()
.includes(keyword.value.toLocaleLowerCase().trim())
resultOptions.value = flatMenusData.filter(menu =>
keyword.value
? transformI18n(menu.meta?.title)
.toLocaleLowerCase()
.includes(keyword.value.toLocaleLowerCase().trim()) ||
(locale.value === "zh" &&
!isAllEmpty(
match(
transformI18n(menu.meta?.title).toLocaleLowerCase(),
keyword.value.toLocaleLowerCase().trim()
)
))
: false
);
if (resultOptions.value?.length > 0) {
activePath.value = resultOptions.value[0].path;
@@ -83,6 +95,11 @@ function handleClose() {
}, 200);
}
function scrollTo(index) {
const scrollTop = resultRef.value.handleScroll(index);
scrollbarRef.value.setScrollTop(scrollTop);
}
/** key up */
function handleUp() {
const { length } = resultOptions.value;
@@ -92,8 +109,10 @@ function handleUp() {
);
if (index === 0) {
activePath.value = resultOptions.value[length - 1].path;
scrollTo(resultOptions.value.length - 1);
} else {
activePath.value = resultOptions.value[index - 1].path;
scrollTo(index - 1);
}
}
@@ -109,6 +128,7 @@ function handleDown() {
} else {
activePath.value = resultOptions.value[index + 1].path;
}
scrollTo(index + 1);
}
/** key enter */
@@ -127,41 +147,56 @@ onKeyStroke("ArrowDown", handleDown);
<template>
<el-dialog
top="5vh"
class="pure-search-dialog"
v-model="show"
:width="device === 'mobile' ? '80vw' : '50vw'"
:show-close="false"
:width="device === 'mobile' ? '80vw' : '40vw'"
:before-close="handleClose"
:style="{
borderRadius: '6px'
}"
append-to-body
@opened="inputRef.focus()"
@closed="inputRef.blur()"
>
<el-input
ref="inputRef"
size="large"
v-model="keyword"
clearable
placeholder="请输入关键词搜索"
placeholder="搜索菜单"
@input="handleSearch"
>
<template #prefix>
<span class="el-input__icon">
<IconifyIconOffline :icon="Search" />
</span>
<IconifyIconOffline
:icon="Search"
class="text-primary w-[24px] h-[24px]"
/>
</template>
</el-input>
<div class="search-result-container">
<el-empty v-if="resultOptions.length === 0" description="暂无搜索结果" />
<SearchResult
v-else
v-model:value="activePath"
:options="resultOptions"
@click="handleEnter"
/>
<el-scrollbar ref="scrollbarRef" max-height="calc(90vh - 140px)">
<el-empty
v-if="resultOptions.length === 0"
description="暂无搜索结果"
/>
<SearchResult
v-else
ref="resultRef"
v-model:value="activePath"
:options="resultOptions"
@click="handleEnter"
/>
</el-scrollbar>
</div>
<template #footer>
<SearchFooter />
<SearchFooter :total="resultOptions.length" />
</template>
</el-dialog>
</template>
<style lang="scss" scoped>
.search-result-container {
margin-top: 20px;
margin-top: 12px;
}
</style>

View File

@@ -1,13 +1,12 @@
<script setup lang="ts">
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { transformI18n } from "@/plugins/i18n";
import { useResizeObserver } from "@vueuse/core";
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { ref, computed, getCurrentInstance, onMounted } from "vue";
import enterOutlined from "@/assets/svg/enter_outlined.svg?component";
import Bookmark2Line from "@iconify-icons/ri/bookmark-2-line";
const { t } = useI18n();
interface optionsItem {
path: string;
meta?: {
@@ -26,8 +25,11 @@ interface Emits {
(e: "enter"): void;
}
const resultRef = ref();
const innerHeight = ref();
const props = withDefaults(defineProps<Props>(), {});
const emit = defineEmits<Emits>();
const instance = getCurrentInstance()!;
const itemStyle = computed(() => {
return item => {
@@ -57,22 +59,48 @@ async function handleMouse(item) {
function handleTo() {
emit("enter");
}
function resizeResult() {
// el-scrollbar max-height="calc(90vh - 140px)"
innerHeight.value = window.innerHeight - window.innerHeight / 10 - 140;
}
useResizeObserver(resultRef, () => {
resizeResult();
});
function handleScroll(index: number) {
const curInstance = instance?.proxy?.$refs[`resultItemRef${index}`];
if (!curInstance) return 0;
const curRef = curInstance[0] as ElRef;
const scrollTop = curRef.offsetTop + 128; // 128 两个result-item56px+56px=112px高度加上下margin8px+8px=16px
return scrollTop > innerHeight.value ? scrollTop - innerHeight.value : 0;
}
onMounted(() => {
resizeResult();
});
defineExpose({ handleScroll });
</script>
<template>
<div class="result">
<template v-for="item in options" :key="item.path">
<div
class="result-item dark:bg-[#1d1d1d]"
:style="itemStyle(item)"
@click="handleTo"
@mouseenter="handleMouse(item)"
>
<component :is="useRenderIcon(item.meta?.icon ?? Bookmark2Line)" />
<span class="result-item-title">{{ t(item.meta?.title) }}</span>
<enterOutlined />
</div>
</template>
<div ref="resultRef" class="result">
<div
v-for="(item, index) in options"
:key="item.path"
:ref="'resultItemRef' + index"
class="result-item dark:bg-[#1d1d1d]"
:style="itemStyle(item)"
@click="handleTo"
@mouseenter="handleMouse(item)"
>
<component :is="useRenderIcon(item.meta?.icon ?? Bookmark2Line)" />
<span class="result-item-title">
{{ transformI18n(item.meta?.title) }}
</span>
<enterOutlined />
</div>
</div>
</template>

View File

@@ -218,7 +218,6 @@ watch($storage, ({ layout }) => {
});
onBeforeMount(() => {
dataThemeChange();
/* 初始化项目配置 */
nextTick(() => {
settings.greyVal &&

View File

@@ -1,8 +1,9 @@
<script setup lang="ts">
import Search from "../search/index.vue";
import Notice from "../notice/index.vue";
import { ref, watch, nextTick } from "vue";
import SidebarItem from "./sidebarItem.vue";
import { isAllEmpty } from "@pureadmin/utils";
import { ref, nextTick, computed } from "vue";
import { useNav } from "@/layout/hooks/useNav";
import { useTranslationLang } from "../../hooks/useTranslationLang";
import { usePermissionStoreHook } from "@/store/modules/permission";
@@ -17,27 +18,23 @@ const { t, route, locale, translationCh, translationEn } =
useTranslationLang(menuRef);
const {
title,
routers,
logout,
backTopMenu,
onPanel,
menuSelect,
username,
userAvatar,
avatarsStyle,
getDropdownItemStyle,
getDropdownItemClass
} = useNav();
const defaultActive = computed(() =>
!isAllEmpty(route.meta?.activePath) ? route.meta.activePath : route.path
);
nextTick(() => {
menuRef.value?.handleResize();
});
watch(
() => route.path,
() => {
menuSelect(route.path, routers);
}
);
</script>
<template>
@@ -54,8 +51,7 @@ watch(
ref="menuRef"
mode="horizontal"
class="horizontal-header-menu"
:default-active="route.path"
@select="indexPath => menuSelect(indexPath, routers)"
:default-active="defaultActive"
>
<sidebar-item
v-for="route in usePermissionStoreHook().wholeMenus"
@@ -102,10 +98,7 @@ watch(
<!-- 退出登录 -->
<el-dropdown trigger="click">
<span class="el-dropdown-link navbar-bg-hover">
<img
src="https://avatars.githubusercontent.com/u/44761321?v=4"
:style="avatarsStyle"
/>
<img :src="userAvatar" :style="avatarsStyle" />
<p v-if="username" class="dark:text-white">{{ username }}</p>
</span>
<template #dropdown>

View File

@@ -7,7 +7,6 @@ const props = defineProps({
});
const { title } = useNav();
const topPath = getTopMenu().path;
</script>
<template>
@@ -18,7 +17,7 @@ const topPath = getTopMenu().path;
key="props.collapse"
:title="title"
class="sidebar-logo-link"
:to="topPath"
:to="getTopMenu()?.path ?? '/'"
>
<img src="/logo.svg" alt="logo" />
<span class="sidebar-title">{{ title }}</span>
@@ -28,7 +27,7 @@ const topPath = getTopMenu().path;
key="expand"
:title="title"
class="sidebar-logo-link"
:to="topPath"
:to="getTopMenu()?.path ?? '/'"
>
<img src="/logo.svg" alt="logo" />
<span class="sidebar-title">{{ title }}</span>

View File

@@ -2,6 +2,7 @@
import extraIcon from "./extraIcon.vue";
import Search from "../search/index.vue";
import Notice from "../notice/index.vue";
import { isAllEmpty } from "@pureadmin/utils";
import { useNav } from "@/layout/hooks/useNav";
import { transformI18n } from "@/plugins/i18n";
import { ref, toRaw, watch, onMounted, nextTick } from "vue";
@@ -21,12 +22,11 @@ const { t, route, locale, translationCh, translationEn } =
useTranslationLang(menuRef);
const {
device,
routers,
logout,
onPanel,
menuSelect,
resolvePath,
username,
userAvatar,
getDivStyle,
avatarsStyle,
getDropdownItemStyle,
@@ -37,10 +37,9 @@ function getDefaultActive(routePath) {
const wholeMenus = usePermissionStoreHook().wholeMenus;
/** 当前路由的父级路径 */
const parentRoutes = getParentPaths(routePath, wholeMenus)[0];
defaultActive.value = findRouteByPath(
parentRoutes,
wholeMenus
)?.children[0]?.path;
defaultActive.value = !isAllEmpty(route.meta?.activePath)
? route.meta.activePath
: findRouteByPath(parentRoutes, wholeMenus)?.children[0]?.path;
}
onMounted(() => {
@@ -71,7 +70,6 @@ watch(
mode="horizontal"
class="horizontal-header-menu"
:default-active="defaultActive"
@select="indexPath => menuSelect(indexPath, routers)"
>
<el-menu-item
v-for="route in usePermissionStoreHook().wholeMenus"
@@ -134,10 +132,7 @@ watch(
<!-- 退出登录 -->
<el-dropdown trigger="click">
<span class="el-dropdown-link navbar-bg-hover select-none">
<img
src="https://avatars.githubusercontent.com/u/44761321?v=4"
:style="avatarsStyle"
/>
<img :src="userAvatar" :style="avatarsStyle" />
<p v-if="username" class="dark:text-white">{{ username }}</p>
</span>
<template #dropdown>

View File

@@ -5,11 +5,11 @@ import { emitter } from "@/utils/mitt";
import SidebarItem from "./sidebarItem.vue";
import leftCollapse from "./leftCollapse.vue";
import { useNav } from "@/layout/hooks/useNav";
import { storageLocal } from "@pureadmin/utils";
import { responsiveStorageNameSpace } from "@/config";
import { ref, computed, watch, onBeforeMount } from "vue";
import { storageLocal, isAllEmpty } from "@pureadmin/utils";
import { findRouteByPath, getParentPaths } from "@/router/utils";
import { usePermissionStoreHook } from "@/store/modules/permission";
import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue";
const route = useRoute();
const showLogo = ref(
@@ -18,8 +18,7 @@ const showLogo = ref(
)?.showLogo ?? true
);
const { routers, device, pureApp, isCollapse, menuSelect, toggleSideBar } =
useNav();
const { device, pureApp, isCollapse, menuSelect, toggleSideBar } = useNav();
const subMenuData = ref([]);
@@ -33,7 +32,13 @@ const loading = computed(() =>
pureApp.layout === "mix" ? false : menuData.value.length === 0 ? true : false
);
function getSubMenuData(path: string) {
const defaultActive = computed(() =>
!isAllEmpty(route.meta?.activePath) ? route.meta.activePath : route.path
);
function getSubMenuData() {
let path = "";
path = defaultActive.value;
subMenuData.value = [];
// path的上级路由组成的数组
const parentPathArr = getParentPaths(
@@ -49,21 +54,27 @@ function getSubMenuData(path: string) {
subMenuData.value = parenetRoute?.children;
}
getSubMenuData(route.path);
watch(
() => [route.path, usePermissionStoreHook().wholeMenus],
() => {
if (route.path.includes("/redirect")) return;
getSubMenuData();
menuSelect(route.path);
}
);
onMounted(() => {
getSubMenuData();
onBeforeMount(() => {
emitter.on("logoChange", key => {
showLogo.value = key;
});
});
watch(
() => [route.path, usePermissionStoreHook().wholeMenus],
() => {
getSubMenuData(route.path);
menuSelect(route.path, routers);
}
);
onBeforeUnmount(() => {
// 解绑`logoChange`公共事件,防止多次触发
emitter.off("logoChange");
});
</script>
<template>
@@ -82,9 +93,8 @@ watch(
mode="vertical"
class="outer-most select-none"
:collapse="isCollapse"
:default-active="route.path"
:default-active="defaultActive"
:collapse-transition="false"
@select="indexPath => menuSelect(indexPath, routers)"
>
<sidebar-item
v-for="routes in menuData"

View File

@@ -4,12 +4,12 @@ import { emitter } from "@/utils/mitt";
import { RouteConfigs } from "../../types";
import { useTags } from "../../hooks/useTag";
import { routerArrays } from "@/layout/types";
import { isEqual, isAllEmpty } from "@pureadmin/utils";
import { handleAliveRoute, getTopMenu } from "@/router/utils";
import { useSettingStoreHook } from "@/store/modules/settings";
import { useResizeObserver, useFullscreen } from "@vueuse/core";
import { isEqual, isAllEmpty, debounce } from "@pureadmin/utils";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { ref, watch, unref, toRaw, nextTick, onBeforeMount } from "vue";
import { useResizeObserver, useDebounceFn, useFullscreen } from "@vueuse/core";
import { ref, watch, unref, toRaw, nextTick, onBeforeUnmount } from "vue";
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
@@ -50,24 +50,26 @@ const tabDom = ref();
const containerDom = ref();
const scrollbarDom = ref();
const isShowArrow = ref(false);
const topPath = getTopMenu().path;
const topPath = getTopMenu()?.path;
const { VITE_HIDE_HOME } = import.meta.env;
const { isFullscreen, toggle } = useFullscreen();
const dynamicTagView = () => {
const dynamicTagView = async () => {
await nextTick();
const index = multiTags.value.findIndex(item => {
if (item.query) {
if (!isAllEmpty(route.query)) {
return isEqual(route.query, item.query);
} else if (item.params) {
} else if (!isAllEmpty(route.params)) {
return isEqual(route.params, item.params);
} else {
return item.path === route.path;
return route.path === item.path;
}
});
moveToView(index);
};
const moveToView = async (index: number): Promise<void> => {
await nextTick();
const tabNavPadding = 10;
if (!instance.refs["dynamic" + index]) return;
const tabItemEl = instance.refs["dynamic" + index][0];
@@ -78,9 +80,6 @@ const moveToView = async (index: number): Promise<void> => {
? scrollbarDom.value?.offsetWidth
: 0;
// 获取视图更新后dom
await nextTick();
// 已有标签页总长度(包含溢出部分)
const tabDomWidth = tabDom.value ? tabDom.value?.offsetWidth : 0;
@@ -135,31 +134,29 @@ const handleScroll = (offset: number): void => {
}
};
function dynamicRouteTag(value: string, parentPath: string): void {
function dynamicRouteTag(value: string): void {
const hasValue = multiTags.value.some(item => {
return item.path === value;
});
function concatPath(arr: object[], value: string, parentPath: string) {
function concatPath(arr: object[], value: string) {
if (!hasValue) {
arr.forEach((arrItem: any) => {
const pathConcat = parentPath + arrItem.path;
if (arrItem.path === value || pathConcat === value) {
if (arrItem.path === value || arrItem.path === value) {
useMultiTagsStoreHook().handleTags("push", {
path: value,
parentPath: `/${parentPath.split("/")[1]}`,
meta: arrItem.meta,
name: arrItem.name
});
} else {
if (arrItem.children && arrItem.children.length > 0) {
concatPath(arrItem.children, value, parentPath);
concatPath(arrItem.children, value);
}
}
});
}
}
concatPath(router.options.routes as any, value, parentPath);
concatPath(router.options.routes as any, value);
}
/** 刷新路由 */
@@ -169,6 +166,7 @@ function onFresh() {
path: "/redirect" + fullPath,
query
});
handleAliveRoute(route as ToRouteType, "refresh");
}
function deleteDynamicTag(obj: any, current: any, tag?: string) {
@@ -241,7 +239,7 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
function deleteMenu(item, tag?: string) {
deleteDynamicTag(item, item.path, tag);
handleAliveRoute(route as toRouteType);
handleAliveRoute(route as ToRouteType);
}
function onClickDrop(key, item, selectRoute?: RouteConfigs) {
@@ -289,7 +287,7 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
length: multiTags.value.length
});
router.push(topPath);
handleAliveRoute(route as toRouteType);
handleAliveRoute(route as ToRouteType);
break;
case 6:
// 整体页面全屏
@@ -464,7 +462,17 @@ function tagOnClick(item) {
// showMenuModel(item?.path, item?.query);
}
onBeforeMount(() => {
watch(route, () => {
activeIndex.value = -1;
dynamicTagView();
});
watch(isFullscreen, () => {
tagsViews[6].icon = Fullscreen;
tagsViews[6].text = $t("buttons.hswholeFullScreen");
});
onMounted(() => {
if (!instance) return;
// 根据当前路由初始化操作标签页的禁用状态
@@ -482,32 +490,25 @@ onBeforeMount(() => {
});
// 接收侧边栏切换传递过来的参数
emitter.on("changLayoutRoute", ({ indexPath, parentPath }) => {
dynamicRouteTag(indexPath, parentPath);
emitter.on("changLayoutRoute", indexPath => {
dynamicRouteTag(indexPath);
setTimeout(() => {
showMenuModel(indexPath);
});
});
});
watch([route], () => {
activeIndex.value = -1;
dynamicTagView();
});
watch(isFullscreen, () => {
tagsViews[6].icon = Fullscreen;
tagsViews[6].text = $t("buttons.hswholeFullScreen");
});
onMounted(() => {
useResizeObserver(
scrollbarDom,
useDebounceFn(() => {
dynamicTagView();
}, 200)
debounce(() => dynamicTagView())
);
});
onBeforeUnmount(() => {
// 解绑`tagViewsChange`、`tagViewsShowModel`、`changLayoutRoute`公共事件,防止多次触发
emitter.off("tagViewsChange");
emitter.off("tagViewsShowModel");
emitter.off("changLayoutRoute");
});
</script>
<template>
@@ -524,7 +525,7 @@ onMounted(() => {
:class="[
'scroll-item is-closable',
linkIsActive(item),
$route.path === item.path && showModel === 'card'
route.path === item.path && showModel === 'card'
? 'card-active'
: ''
]"

View File

@@ -3,6 +3,7 @@ import { getConfig } from "@/config";
import { useRouter } from "vue-router";
import { emitter } from "@/utils/mitt";
import { routeMetaType } from "../types";
import userAvatar from "@/assets/user.jpg";
import { getTopMenu } from "@/router/utils";
import { useGlobal } from "@pureadmin/utils";
import { transformI18n } from "@/plugins/i18n";
@@ -87,7 +88,7 @@ export function useNav() {
}
function backTopMenu() {
router.push(getTopMenu().path);
router.push(getTopMenu()?.path);
}
function onPanel() {
@@ -113,38 +114,13 @@ export function useNav() {
}
}
function menuSelect(indexPath: string, routers): void {
if (wholeMenus.value.length === 0) return;
if (isRemaining(indexPath)) return;
let parentPath = "";
const parentPathIndex = indexPath.lastIndexOf("/");
if (parentPathIndex > 0) {
parentPath = indexPath.slice(0, parentPathIndex);
}
/** 找到当前路由的信息 */
function findCurrentRoute(indexPath: string, routes) {
if (!routes) return console.error(errorInfo);
return routes.map(item => {
if (item.path === indexPath) {
if (item.redirect) {
findCurrentRoute(item.redirect, item.children);
} else {
/** 切换左侧菜单 通知标签页 */
emitter.emit("changLayoutRoute", {
indexPath,
parentPath
});
}
} else {
if (item.children) findCurrentRoute(indexPath, item.children);
}
});
}
findCurrentRoute(indexPath, routers);
function menuSelect(indexPath: string) {
if (wholeMenus.value.length === 0 || isRemaining(indexPath)) return;
emitter.emit("changLayoutRoute", indexPath);
}
/** 判断路径是否参与菜单 */
function isRemaining(path: string): boolean {
function isRemaining(path: string) {
return remainingPaths.includes(path);
}
@@ -166,6 +142,7 @@ export function useNav() {
isCollapse,
pureApp,
username,
userAvatar,
avatarsStyle,
tooltipEffect,
getDropdownItemStyle,

View File

@@ -1,7 +1,7 @@
import { useNav } from "./useNav";
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
import { watch, type Ref } from "vue";
import { watch, onBeforeMount, type Ref } from "vue";
export function useTranslationLang(ref?: Ref) {
const { $storage, changeTitle, handleResize } = useNav();
@@ -27,6 +27,10 @@ export function useTranslationLang(ref?: Ref) {
}
);
onBeforeMount(() => {
locale.value = $storage.locale?.locale ?? "zh";
});
return {
t,
route,

View File

@@ -3,12 +3,21 @@ import "animate.css";
// 引入 src/components/ReIcon/src/offlineIcon.ts 文件中所有使用addIcon添加过的本地图标
import "@/components/ReIcon/src/offlineIcon";
import { setType } from "./types";
import { emitter } from "@/utils/mitt";
import { useLayout } from "./hooks/useLayout";
import { useResizeObserver } from "@vueuse/core";
import { useAppStoreHook } from "@/store/modules/app";
import { useSettingStoreHook } from "@/store/modules/settings";
import { deviceDetection, useDark, useGlobal } from "@pureadmin/utils";
import { h, reactive, computed, onMounted, defineComponent } from "vue";
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
import {
h,
ref,
reactive,
computed,
onMounted,
onBeforeMount,
defineComponent
} from "vue";
import navbar from "./components/navbar.vue";
import tag from "./components/tag/index.vue";
@@ -18,6 +27,7 @@ import Vertical from "./components/sidebar/vertical.vue";
import Horizontal from "./components/sidebar/horizontal.vue";
import backTop from "@/assets/svg/back_top.svg?component";
const appWrapperRef = ref();
const { isDark } = useDark();
const { layout } = useLayout();
const isMobile = deviceDetection();
@@ -70,10 +80,10 @@ function toggle(device: string, bool: boolean) {
// 判断是否可自动关闭菜单栏
let isAutoCloseSidebar = true;
// 监听容器
emitter.on("resize", ({ detail }) => {
useResizeObserver(appWrapperRef, entries => {
if (isMobile) return;
const { width } = detail;
const entry = entries[0];
const { width } = entry.contentRect;
width <= 760 ? setTheme("vertical") : setTheme(useAppStoreHook().layout);
/** width app-wrapper类容器宽度
* 0 < width <= 760 隐藏侧边栏
@@ -88,11 +98,12 @@ emitter.on("resize", ({ detail }) => {
toggle("desktop", false);
isAutoCloseSidebar = false;
}
} else if (width > 990) {
if (!set.sidebar.isClickCollapse) {
toggle("desktop", true);
isAutoCloseSidebar = true;
}
} else if (width > 990 && !set.sidebar.isClickCollapse) {
toggle("desktop", true);
isAutoCloseSidebar = true;
} else {
toggle("desktop", false);
isAutoCloseSidebar = false;
}
});
@@ -102,6 +113,10 @@ onMounted(() => {
}
});
onBeforeMount(() => {
useDataThemeChange().dataThemeChange();
});
const layoutHeader = defineComponent({
render() {
return h(
@@ -134,7 +149,7 @@ const layoutHeader = defineComponent({
</script>
<template>
<div :class="['app-wrapper', set.classes]" v-resize>
<div ref="appWrapperRef" :class="['app-wrapper', set.classes]">
<div
v-show="
set.device === 'mobile' &&

View File

@@ -6,7 +6,6 @@ export const routerArrays: Array<RouteConfigs> =
? [
{
path: "/welcome",
parentPath: "/",
meta: {
title: "menus.hshome",
icon: "homeFilled"
@@ -25,7 +24,6 @@ export type routeMetaType = {
export type RouteConfigs = {
path?: string;
parentPath?: string;
query?: object;
params?: object;
meta?: routeMetaType;

View File

@@ -23,7 +23,7 @@ import {
formatFlatteningRoutes
} from "./utils";
import { buildHierarchyTree } from "@/utils/tree";
import { isUrl, openLink, storageSession } from "@pureadmin/utils";
import { isUrl, openLink, storageSession, isAllEmpty } from "@pureadmin/utils";
import remainingRouter from "./modules/remaining";
@@ -47,13 +47,13 @@ Object.keys(modules).forEach(key => {
/** 导出处理后的静态路由(三级及以上的路由全部拍成二级) */
export const constantRoutes: Array<RouteRecordRaw> = formatTwoStageRoutes(
formatFlatteningRoutes(buildHierarchyTree(ascending(routes)))
formatFlatteningRoutes(buildHierarchyTree(ascending(routes.flat(Infinity))))
);
/** 用于渲染菜单,保持原始层级 */
export const constantMenus: Array<RouteComponent> = ascending(routes).concat(
...remainingRouter
);
export const constantMenus: Array<RouteComponent> = ascending(
routes.flat(Infinity)
).concat(...remainingRouter);
/** 不参与菜单的路由 */
export const remainingPaths = Object.keys(remainingRouter).map(v => {
@@ -87,7 +87,9 @@ export function resetRouter() {
if (name && router.hasRoute(name) && meta?.backstage) {
router.removeRoute(name);
router.options.routes = formatTwoStageRoutes(
formatFlatteningRoutes(buildHierarchyTree(ascending(routes)))
formatFlatteningRoutes(
buildHierarchyTree(ascending(routes.flat(Infinity)))
)
);
}
});
@@ -99,7 +101,7 @@ const whiteList = ["/login"];
const { VITE_HIDE_HOME } = import.meta.env;
router.beforeEach((to: toRouteType, _from, next) => {
router.beforeEach((to: ToRouteType, _from, next) => {
if (to.meta?.keepAlive) {
handleAliveRoute(to, "add");
// 页面整体刷新和点击标签页刷新
@@ -156,14 +158,26 @@ router.beforeEach((to: toRouteType, _from, next) => {
getTopMenu(true);
// query、params模式路由传参数的标签页不在此处处理
if (route && route.meta?.title) {
useMultiTagsStoreHook().handleTags("push", {
path: route.path,
name: route.name,
meta: route.meta
});
if (isAllEmpty(route.parentId) && route.meta?.backstage) {
// 此处为动态顶级路由(目录)
const { path, name, meta } = route.children[0];
useMultiTagsStoreHook().handleTags("push", {
path,
name,
meta
});
} else {
const { path, name, meta } = route;
useMultiTagsStoreHook().handleTags("push", {
path,
name,
meta
});
}
}
}
router.push(to.fullPath);
// 确保动态路由完全加入路由列表并且不影响静态路由注意动态路由刷新时router.beforeEach可能会触发两次第一次触发动态路由还未完全添加第二次动态路由才完全添加到路由列表如果需要在router.beforeEach做一些判断可以在to.name存在的条件下去判断这样就只会触发一次
if (isAllEmpty(to.name)) router.push(to.fullPath);
});
}
toCorrectRoute();

View File

@@ -10,6 +10,15 @@ export default {
rank: able
},
children: [
{
path: "/able/directives",
name: "Directives",
component: () => import("@/views/able/directives.vue"),
meta: {
title: $t("menus.hsOptimize"),
extraIcon: "IF-pure-iconfont-new svg"
}
},
{
path: "/able/watermark",
name: "WaterMark",
@@ -137,6 +146,24 @@ export default {
meta: {
title: $t("menus.hsInfiniteScroll")
}
},
{
path: "/able/sensitive",
name: "Sensitive",
component: () => import("@/views/able/sensitive.vue"),
meta: {
title: $t("menus.hsSensitive"),
extraIcon: "IF-pure-iconfont-new svg"
}
},
{
path: "/able/pinyin",
name: "Pinyin",
component: () => import("@/views/able/pinyin.vue"),
meta: {
title: $t("menus.hsPinyin"),
extraIcon: "IF-pure-iconfont-new svg"
}
}
]
} as RouteConfigsTable;

View File

@@ -3,7 +3,7 @@ import { components } from "@/router/enums";
export default {
path: "/components",
redirect: "/components/video",
redirect: "/components/dialog",
meta: {
icon: "menu",
title: $t("menus.hscomponents"),
@@ -12,7 +12,7 @@ export default {
children: [
{
path: "/components/dialog",
name: "Dialog",
name: "DialogPage",
component: () => import("@/views/components/dialog/index.vue"),
meta: {
title: $t("menus.hsdialog"),
@@ -31,9 +31,27 @@ export default {
title: $t("menus.hsmessage")
}
},
{
path: "/components/segmented",
name: "Segmented",
component: () => import("@/views/components/segmented/index.vue"),
meta: {
title: $t("menus.hssegmented"),
extraIcon: "IF-pure-iconfont-new svg"
}
},
{
path: "/components/waterfall",
name: "Waterfall",
component: () => import("@/views/components/waterfall/index.vue"),
meta: {
title: $t("menus.hswaterfall"),
extraIcon: "IF-pure-iconfont-new svg"
}
},
{
path: "/components/video",
name: "Video",
name: "VideoPage",
component: () => import("@/views/components/video/index.vue"),
meta: {
title: $t("menus.hsvideo")
@@ -41,7 +59,7 @@ export default {
},
{
path: "/components/map",
name: "Map",
name: "MapPage",
component: () => import("@/views/components/map/index.vue"),
meta: {
title: $t("menus.hsmap"),
@@ -73,7 +91,7 @@ export default {
},
{
path: "/components/button",
name: "Button",
name: "ButtonPage",
component: () => import("@/views/components/button/index.vue"),
meta: {
title: $t("menus.hsbutton")

View File

@@ -256,7 +256,7 @@ function formatTwoStageRoutes(routesList: RouteRecordRaw[]) {
}
/** 处理缓存路由(添加、删除、刷新) */
function handleAliveRoute({ name }: toRouteType, mode?: string) {
function handleAliveRoute({ name }: ToRouteType, mode?: string) {
switch (mode) {
case "add":
usePermissionStoreHook().cacheOperate({
@@ -270,6 +270,12 @@ function handleAliveRoute({ name }: toRouteType, mode?: string) {
name
});
break;
case "refresh":
usePermissionStoreHook().cacheOperate({
mode: "refresh",
name
});
break;
default:
usePermissionStoreHook().cacheOperate({
mode: "delete",

View File

@@ -2,8 +2,8 @@ import { defineStore } from "pinia";
import { store } from "@/store";
import { cacheType } from "./types";
import { constantMenus } from "@/router";
import { getKeyList } from "@pureadmin/utils";
import { useMultiTagsStoreHook } from "./multiTags";
import { debounce, getKeyList } from "@pureadmin/utils";
import { ascending, filterTree, filterNoPermissionTree } from "@/router/utils";
export const usePermissionStore = defineStore({
@@ -24,18 +24,20 @@ export const usePermissionStore = defineStore({
);
},
cacheOperate({ mode, name }: cacheType) {
const delIndex = this.cachePageList.findIndex(v => v === name);
switch (mode) {
case "refresh":
this.cachePageList = this.cachePageList.filter(v => v !== name);
break;
case "add":
this.cachePageList.push(name);
break;
case "delete":
// eslint-disable-next-line no-case-declarations
const delIndex = this.cachePageList.findIndex(v => v === name);
delIndex !== -1 && this.cachePageList.splice(delIndex, 1);
break;
}
/** 监听缓存页面是否存在于标签页,不存在则删除 */
(() => {
debounce(() => {
let cacheLength = this.cachePageList.length;
const nameList = getKeyList(useMultiTagsStoreHook().multiTags, "name");
while (cacheLength > 0) {

View File

@@ -24,7 +24,6 @@ export type appType = {
export type multiType = {
path: string;
parentPath: string;
name: string;
meta: any;
query?: object;

View File

@@ -110,6 +110,10 @@ html.dark {
&:hover {
color: rgb(255 255 255 / 85%) !important;
background-color: rgb(255 255 255 / 12%);
.pure-dialog-svg {
color: rgb(255 255 255 / 85%) !important;
}
}
}
}
@@ -134,4 +138,35 @@ html.dark {
}
}
}
/* 自定义菜单搜索样式 */
.pure-search-dialog {
.el-dialog__footer {
box-shadow: 0 -1px 0 0 #555a64, 0 -3px 6px 0 rgb(69 98 155 / 12%);
}
.search-footer {
.search-footer-item {
color: rgb(235 235 235 / 60%);
.icon {
box-shadow: none;
}
}
}
}
/* ReSegmented 组件 */
.pure-segmented {
color: rgb(255 255 255 / 65%);
background-color: #000;
.pure-segmented-item-selected {
background-color: #1f1f1f;
}
.pure-segmented-item-disabled {
color: rgb(255 255 255 / 25%);
}
}
}

View File

@@ -69,6 +69,19 @@
}
}
.pure-dialog {
.pure-dialog-svg {
color: var(--el-color-info);
}
.el-dialog__headerbtn {
top: 20px;
right: 14px;
width: 24px;
height: 24px;
}
}
/* 全局覆盖element-plus的el-dialog、el-drawer、el-message-box、el-notification组件右上角关闭图标的样式表现更鲜明 */
.el-dialog__headerbtn,
.el-message-box__headerbtn {
@@ -94,6 +107,10 @@
color: rgb(0 0 0 / 88%) !important;
text-decoration: none;
background-color: rgb(0 0 0 / 6%);
.pure-dialog-svg {
color: rgb(0 0 0 / 88%) !important;
}
}
}
}
@@ -131,3 +148,24 @@
}
}
}
/* 自定义菜单搜索样式 */
.pure-search-dialog {
.el-dialog__header {
display: none;
}
.el-dialog__body {
padding-top: 12px;
padding-bottom: 0;
}
.el-input__inner {
font-size: 1.2em;
}
.el-dialog__footer {
padding-bottom: 10px;
box-shadow: 0 -1px 0 0 #e0e3e8, 0 -3px 6px 0 rgb(69 98 155 / 12%);
}
}

View File

@@ -63,7 +63,7 @@ class PureHttp {
async (config: PureHttpRequestConfig): Promise<any> => {
// 开启进度条动画
NProgress.start();
// 优先判断post/get等方法是否传入回,否则执行初始化设置等回
// 优先判断post/get等方法是否传入回,否则执行初始化设置等回
if (typeof config.beforeRequestCallback === "function") {
config.beforeRequestCallback(config);
return config;
@@ -123,7 +123,7 @@ class PureHttp {
const $config = response.config;
// 关闭进度条动画
NProgress.done();
// 优先判断post/get等方法是否传入回,否则执行初始化设置等回
// 优先判断post/get等方法是否传入回,否则执行初始化设置等回
if (typeof $config.beforeResponseCallback === "function") {
$config.beforeResponseCallback(response);
return response.data;
@@ -159,7 +159,7 @@ class PureHttp {
...axiosConfig
} as PureHttpRequestConfig;
// 单独处理自定义请求/响应回
// 单独处理自定义请求/响应回
return new Promise((resolve, reject) => {
PureHttp.axiosInstance
.request(config)

View File

@@ -1,21 +1,13 @@
import type { Emitter } from "mitt";
import mitt from "mitt";
/** 全局公共事件需要在此处添加类型 */
type Events = {
resize: {
detail: {
width: number;
height: number;
};
};
openPanel: string;
tagViewsChange: string;
tagViewsShowModel: string;
logoChange: boolean;
changLayoutRoute: {
indexPath: string;
parentPath: string;
};
changLayoutRoute: string;
};
export const emitter: Emitter<Events> = mitt<Events>();

View File

@@ -0,0 +1,156 @@
<script setup lang="ts">
import { ref } from "vue";
import { message } from "@/utils/message";
defineOptions({
name: "Directives"
});
const search = ref("");
const searchTwo = ref("");
const searchThree = ref("");
const searchFour = ref("");
const searchFive = ref("");
const searchSix = ref("copy");
const text = ref("可复制的文本");
const long = ref(false);
const cbText = ref("");
const idx = ref(0);
function onInput() {
message(search.value);
}
function onInputTwo() {
message(searchTwo.value);
}
function onInputThree({ name, sex }) {
message(`${name}${sex}${searchThree.value}`);
}
function onInputFour() {
message(searchFour.value);
}
function onInputFive({ name, sex }) {
message(`${name}${sex}${searchFive.value}`);
}
function onLongpress() {
long.value = true;
}
function onCustomLongpress() {
long.value = true;
}
function onCbLongpress() {
idx.value += 1;
long.value = true;
cbText.value = `持续回调${idx.value}`;
}
function onReset() {
long.value = false;
cbText.value = "";
idx.value = 0;
}
</script>
<template>
<el-card shadow="never">
<template #header>
<div class="card-header">
<span class="font-medium">自定义防抖截流文本复制长按指令</span>
</div>
</template>
<div class="mb-2">
防抖指令连续输入只会执行第一次点击事件立即执行
<el-input
v-optimize="{
event: 'input',
fn: onInput,
immediate: true,
timeout: 1000
}"
v-model="search"
class="!w-[200px]"
clearable
@clear="onInput"
/>
</div>
<div class="mb-2">
防抖指令(连续输入,只会执行最后一次事件,延后执行)
<el-input
v-optimize="{ event: 'input', fn: onInputTwo, timeout: 400 }"
v-model="searchTwo"
class="!w-[200px]"
clearable
/>
</div>
<div>
防抖指令(连续输入,只会执行最后一次事件,延后执行,传参用法)
<el-input
v-optimize="{
event: 'input',
fn: onInputThree,
timeout: 400,
params: { name: '小明', sex: '男' }
}"
v-model="searchThree"
class="!w-[200px]"
clearable
/>
</div>
<el-divider />
<div class="mb-2">
节流指令(连续输入,每一秒只会执行一次事件)
<el-input
v-optimize:throttle="{ event: 'input', fn: onInputFour, timeout: 1000 }"
v-model="searchFour"
class="!w-[200px]"
clearable
/>
</div>
<div>
节流指令(连续输入,每一秒只会执行一次事件,传参用法)
<el-input
v-optimize:throttle="{
event: 'input',
fn: onInputFive,
params: { name: '小明', sex: '男' }
}"
v-model="searchFive"
class="!w-[200px]"
clearable
/>
</div>
<el-divider />
<div class="mb-2">
文本复制指令(双击输入框内容即可复制)
<el-input v-copy="searchSix" v-model="searchSix" class="!w-[200px]" />
</div>
<div>
文本复制指令(自定义触发事件,单击复制)
<span v-copy:click="text" class="text-sky-500">{{ text }}</span>
</div>
<el-divider />
<el-space wrap>
长按指令
<el-button v-longpress="onLongpress">长按默认500ms</el-button>
<el-button v-longpress:1000="onCustomLongpress">
自定义长按时长1000ms
</el-button>
<el-button v-longpress:2000:200="onCbLongpress">
2秒后每200ms持续回调
</el-button>
<el-button @click="onReset"> 重置状态 </el-button>
<el-tag :type="long ? 'success' : 'info'" class="ml-2" size="large">
{{ long ? "当前为长按状态" : "当前非长按状态" }}
</el-tag>
<el-tag v-if="cbText" type="danger" class="ml-2" size="large">
{{ cbText }}
</el-tag>
</el-space>
</el-card>
</template>

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