Compare commits
29 Commits
Author | SHA1 | Date |
---|---|---|
|
9caa008551 | |
|
7c18060e18 | |
|
4891f49b7d | |
|
07ca222ee0 | |
|
bfda9f6485 | |
|
67a202fef3 | |
|
48c5c9af8a | |
|
1af33d8a7e | |
|
9eb1356c4e | |
|
6a83d08309 | |
|
607b73a7b3 | |
|
a1e89b7c8a | |
|
d93d1ee6a0 | |
|
96c5a968ef | |
|
c954f2b7e0 | |
|
1df9644a11 | |
|
47f4b5a8ae | |
|
fbe68ba683 | |
|
9652712677 | |
|
7959b6c62a | |
|
25e416e3bb | |
|
5d1356c4a9 | |
|
b0daac8471 | |
|
d46119e174 | |
|
7efed9ed12 | |
|
5f2048d52d | |
|
97c293bc6e | |
|
c3a2cc0a5f | |
|
255843d4c0 |
13
.env.base
13
.env.base
|
@ -1,11 +1,20 @@
|
|||
# 环境
|
||||
NODE_ENV=development
|
||||
VITE_NODE_ENV=development
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_BASE_PATH=base
|
||||
VITE_API_BASE_PATH=
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/
|
||||
|
||||
# 标题
|
||||
VITE_APP_TITLE=ElementAdmin
|
||||
|
||||
# 是否全量引入element-plus样式
|
||||
VITE_USE_ALL_ELEMENT_PLUS_STYLE=true
|
||||
|
||||
# 是否开启mock
|
||||
VITE_USE_MOCK=true
|
||||
|
||||
# 是否使用在线图标
|
||||
VITE_USE_ONLINE_ICON=true
|
19
.env.dev
19
.env.dev
|
@ -1,8 +1,8 @@
|
|||
# 环境
|
||||
NODE_ENV=production
|
||||
VITE_NODE_ENV=production
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_BASE_PATH=dev
|
||||
VITE_API_BASE_PATH=
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/dist-dev/
|
||||
|
@ -21,3 +21,18 @@ VITE_OUT_DIR=dist-dev
|
|||
|
||||
# 标题
|
||||
VITE_APP_TITLE=ElementAdmin
|
||||
|
||||
# 是否包分析
|
||||
VITE_USE_BUNDLE_ANALYZER=false
|
||||
|
||||
# 是否全量引入element-plus样式
|
||||
VITE_USE_ALL_ELEMENT_PLUS_STYLE=false
|
||||
|
||||
# 是否开启mock
|
||||
VITE_USE_MOCK=true
|
||||
|
||||
# 是否切割css
|
||||
VITE_USE_CSS_SPLIT=true
|
||||
|
||||
# 是否使用在线图标
|
||||
VITE_USE_ONLINE_ICON=true
|
19
.env.gitee
19
.env.gitee
|
@ -1,8 +1,8 @@
|
|||
# 环境
|
||||
NODE_ENV=production
|
||||
VITE_NODE_ENV=production
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_BASE_PATH=pro
|
||||
VITE_API_BASE_PATH=
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/vue-element-plus-admin/
|
||||
|
@ -21,3 +21,18 @@ VITE_OUT_DIR=dist-pro
|
|||
|
||||
# 标题
|
||||
VITE_APP_TITLE=ElementAdmin
|
||||
|
||||
# 是否包分析
|
||||
VITE_USE_BUNDLE_ANALYZER=false
|
||||
|
||||
# 是否全量引入element-plus样式
|
||||
VITE_USE_ALL_ELEMENT_PLUS_STYLE=false
|
||||
|
||||
# 是否开启mock
|
||||
VITE_USE_MOCK=true
|
||||
|
||||
# 是否切割css
|
||||
VITE_USE_CSS_SPLIT=true
|
||||
|
||||
# 是否使用在线图标
|
||||
VITE_USE_ONLINE_ICON=true
|
19
.env.pro
19
.env.pro
|
@ -1,8 +1,8 @@
|
|||
# 环境
|
||||
NODE_ENV=production
|
||||
VITE_NODE_ENV=production
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_BASE_PATH=pro
|
||||
VITE_API_BASE_PATH=
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/
|
||||
|
@ -21,3 +21,18 @@ VITE_OUT_DIR=dist-pro
|
|||
|
||||
# 标题
|
||||
VITE_APP_TITLE=ElementAdmin
|
||||
|
||||
# 是否包分析
|
||||
VITE_USE_BUNDLE_ANALYZER=true
|
||||
|
||||
# 是否全量引入element-plus样式
|
||||
VITE_USE_ALL_ELEMENT_PLUS_STYLE=false
|
||||
|
||||
# 是否开启mock
|
||||
VITE_USE_MOCK=true
|
||||
|
||||
# 是否切割css
|
||||
VITE_USE_CSS_SPLIT=true
|
||||
|
||||
# 是否使用在线图标
|
||||
VITE_USE_ONLINE_ICON=true
|
19
.env.test
19
.env.test
|
@ -1,8 +1,8 @@
|
|||
# 环境
|
||||
NODE_ENV=production
|
||||
VITE_NODE_ENV=production
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_BASE_PATH=test
|
||||
VITE_API_BASE_PATH=
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/dist-test/
|
||||
|
@ -21,3 +21,18 @@ VITE_OUT_DIR=dist-test
|
|||
|
||||
# 标题
|
||||
VITE_APP_TITLE=ElementAdmin
|
||||
|
||||
# 是否包分析
|
||||
VITE_USE_BUNDLE_ANALYZER=false
|
||||
|
||||
# 是否全量引入element-plus样式
|
||||
VITE_USE_ALL_ELEMENT_PLUS_STYLE=false
|
||||
|
||||
# 是否开启mock
|
||||
VITE_USE_MOCK=true
|
||||
|
||||
# 是否切割css
|
||||
VITE_USE_CSS_SPLIT=false
|
||||
|
||||
# 是否使用在线图标
|
||||
VITE_USE_ONLINE_ICON=true
|
|
@ -24,6 +24,7 @@ module.exports = defineConfig({
|
|||
'plugin:prettier/recommended'
|
||||
],
|
||||
rules: {
|
||||
'vue/no-setup-props-destructure': 'off',
|
||||
'vue/script-setup-uses-vars': 'error',
|
||||
'vue/no-reserved-component-names': 'off',
|
||||
'@typescript-eslint/ban-ts-ignore': 'off',
|
||||
|
|
|
@ -1,130 +0,0 @@
|
|||
name: Automerge
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- labeled
|
||||
- unlabeled
|
||||
- synchronize
|
||||
- opened
|
||||
- edited
|
||||
- ready_for_review
|
||||
- reopened
|
||||
- unlocked
|
||||
pull_request_review:
|
||||
types:
|
||||
- submitted
|
||||
status: {}
|
||||
|
||||
jobs:
|
||||
# 合并发布版本的 pr 到 master
|
||||
auto-merge:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Automerge
|
||||
uses: 'pascalgn/automerge-action@v0.14.3'
|
||||
env:
|
||||
GITHUB_TOKEN: '${{ secrets.TOKEN }}'
|
||||
MERGE_LABELS: ''
|
||||
MERGE_FILTER_AUTHOR: 'kailong321200875'
|
||||
|
||||
push-to-gh-pages:
|
||||
needs: [auto-merge]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: use Node.js 16
|
||||
uses: actions/setup-node@v2.1.2
|
||||
with:
|
||||
node-version: '16.x'
|
||||
|
||||
- name: Set SSH Environment
|
||||
env:
|
||||
DOCS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }}
|
||||
run: |
|
||||
mkdir -p ~/.ssh/
|
||||
echo "$ACTIONS_DEPLOY_KEY" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
ssh-keyscan github.com > ~/.ssh/known_hosts
|
||||
chmod 700 ~/.ssh && chmod 600 ~/.ssh/*
|
||||
git config --local user.email "321200875@qq.com"
|
||||
git config --local user.name "kailong321200875"
|
||||
|
||||
# 发布到 github
|
||||
- name: Build Github
|
||||
run: |
|
||||
pnpm install --no-frozen-lockfile
|
||||
pnpm run build:pro
|
||||
|
||||
- name: Deploy Github
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
deploy_key: ${{secrets.ACTIONS_DEPLOY_KEY}}
|
||||
publish_branch: gh-pages
|
||||
publish_dir: ./dist-pro
|
||||
force_orphan: true
|
||||
cname: element-plus-admin.cn
|
||||
|
||||
push-to-gh-pages-gitee:
|
||||
needs: [auto-merge]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: use Node.js 16
|
||||
uses: actions/setup-node@v2.1.2
|
||||
with:
|
||||
node-version: '16.x'
|
||||
|
||||
- name: Set SSH Environment
|
||||
env:
|
||||
DOCS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }}
|
||||
run: |
|
||||
mkdir -p ~/.ssh/
|
||||
echo "$ACTIONS_DEPLOY_KEY" > ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
ssh-keyscan github.com > ~/.ssh/known_hosts
|
||||
chmod 700 ~/.ssh && chmod 600 ~/.ssh/*
|
||||
git config --local user.email "321200875@qq.com"
|
||||
git config --local user.name "kailong321200875"
|
||||
|
||||
- name: Build Gitee
|
||||
run: |
|
||||
pnpm install --no-frozen-lockfile
|
||||
pnpm run build:gitee
|
||||
|
||||
# 发布到 gitee
|
||||
- name: Deploy Gitee
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
deploy_key: ${{secrets.ACTIONS_DEPLOY_KEY}}
|
||||
publish_branch: gh-pages-gitee
|
||||
publish_dir: ./dist-pro
|
||||
force_orphan: true
|
||||
|
||||
- name: Sync Github Repos To Gitee # 名字随便起
|
||||
uses: Yikun/hub-mirror-action@v1.1 # 使用Yikun/hub-mirror-action
|
||||
with:
|
||||
src: github/kailong321200875 # 源端账户名(github)
|
||||
dst: gitee/kailong110120130 # 目的端账户名(gitee)
|
||||
dst_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} # SSH密钥对中的私钥
|
||||
dst_token: ${{ secrets.GITEE_TOKEN }} # Gitee账户的私人令牌
|
||||
account_type: user # 账户类型
|
||||
clone_style: 'https' # 使用https方式进行clone,也可以使用ssh
|
||||
debug: true # 启用后会显示所有执行命令
|
||||
force_update: true # 启用后,强制同步,即强制覆盖目的端仓库
|
||||
static_list: 'vue-element-plus-admin' # 静态同步列表,在此填写需要同步的仓库名称,可填写多个
|
||||
timeout: '600s' # git超时设置,超时后会自动重试git操作
|
|
@ -1,25 +0,0 @@
|
|||
# on:
|
||||
# push:
|
||||
# branches:
|
||||
# - master
|
||||
|
||||
# jobs:
|
||||
# contrib-readme-en-job:
|
||||
# runs-on: ubuntu-latest
|
||||
# name: A job to automate contrib in readme
|
||||
# steps:
|
||||
# - name: Contribute List
|
||||
# uses: akhilmhdh/contributors-readme-action@v2.3.4
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.CONTRIBUTORS_TOKEN }}
|
||||
|
||||
# contrib-readme-job:
|
||||
# runs-on: ubuntu-latest
|
||||
# name: A job to automate contrib in readme.zh-CN
|
||||
# steps:
|
||||
# - name: Contribute List
|
||||
# uses: akhilmhdh/contributors-readme-action@v2.3.4
|
||||
# with:
|
||||
# readme_path: README.zh-CN.md
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.CONTRIBUTORS_TOKEN }}
|
|
@ -1,18 +0,0 @@
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- release
|
||||
|
||||
name: Release
|
||||
|
||||
jobs:
|
||||
release-please:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: GoogleCloudPlatform/release-please-action@v3
|
||||
id: release
|
||||
with:
|
||||
token: ${{ secrets.TOKEN }}
|
||||
release-type: node
|
||||
package-name: standard-version
|
||||
changelog-types: '[{"type": "types", "section":"Types", "hidden": false},{"type": "revert", "section":"Reverts", "hidden": false},{"type": "feat", "section": "Features", "hidden": false},{"type": "fix", "section": "Bug Fixes", "hidden": false},{"type": "improvement", "section": "Feature Improvements", "hidden": false},{"type": "docs", "section":"Docs", "hidden": false},{"type": "style", "section":"Styling", "hidden": false},{"type": "refactor", "section":"Code Refactoring", "hidden": false},{"type": "perf", "section":"Performance Improvements", "hidden": false},{"type": "test", "section":"Tests", "hidden": false},{"type": "build", "section":"Build System", "hidden": false},{"type": "ci", "section":"CI", "hidden":false}]'
|
|
@ -6,3 +6,4 @@ dist-ssr
|
|||
/dist*
|
||||
*-lock.*
|
||||
pnpm-debug
|
||||
stats.html
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"prettier.enable": false,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"[vue]": {
|
||||
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
|
||||
|
||||
const modules = import.meta.glob('./**/*.ts', {
|
||||
const modules = import.meta.glob('./**/*.mock.ts', {
|
||||
import: 'default',
|
||||
eager: true
|
||||
})
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
import config from '@/config/axios/config'
|
||||
import { MockMethod } from 'vite-plugin-mock'
|
||||
|
||||
const { code } = config
|
||||
|
||||
const timeout = 1000
|
||||
|
||||
export default [
|
||||
// 分析页统计接口
|
||||
{
|
||||
url: '/analysis/total',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: {
|
||||
users: 102400,
|
||||
messages: 81212,
|
||||
moneys: 9280,
|
||||
shoppings: 13600
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 用户来源
|
||||
{
|
||||
url: '/analysis/userAccessSource',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: [
|
||||
{ value: 1000, name: 'analysis.directAccess' },
|
||||
{ value: 310, name: 'analysis.mailMarketing' },
|
||||
{ value: 234, name: 'analysis.allianceAdvertising' },
|
||||
{ value: 135, name: 'analysis.videoAdvertising' },
|
||||
{ value: 1548, name: 'analysis.searchEngines' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 每周用户活跃量
|
||||
{
|
||||
url: '/analysis/weeklyUserActivity',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: [
|
||||
{ value: 13253, name: 'analysis.monday' },
|
||||
{ value: 34235, name: 'analysis.tuesday' },
|
||||
{ value: 26321, name: 'analysis.wednesday' },
|
||||
{ value: 12340, name: 'analysis.thursday' },
|
||||
{ value: 24643, name: 'analysis.friday' },
|
||||
{ value: 1322, name: 'analysis.saturday' },
|
||||
{ value: 1324, name: 'analysis.sunday' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 每月销售额
|
||||
{
|
||||
url: '/analysis/monthlySales',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: [
|
||||
{ estimate: 100, actual: 120, name: 'analysis.january' },
|
||||
{ estimate: 120, actual: 82, name: 'analysis.february' },
|
||||
{ estimate: 161, actual: 91, name: 'analysis.march' },
|
||||
{ estimate: 134, actual: 154, name: 'analysis.april' },
|
||||
{ estimate: 105, actual: 162, name: 'analysis.may' },
|
||||
{ estimate: 160, actual: 140, name: 'analysis.june' },
|
||||
{ estimate: 165, actual: 145, name: 'analysis.july' },
|
||||
{ estimate: 114, actual: 250, name: 'analysis.august' },
|
||||
{ estimate: 163, actual: 134, name: 'analysis.september' },
|
||||
{ estimate: 185, actual: 56, name: 'analysis.october' },
|
||||
{ estimate: 118, actual: 99, name: 'analysis.november' },
|
||||
{ estimate: 123, actual: 123, name: 'analysis.december' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
] as MockMethod[]
|
|
@ -1,219 +0,0 @@
|
|||
import config from '@/config/axios/config'
|
||||
import { MockMethod } from 'vite-plugin-mock'
|
||||
import { toAnyString } from '@/utils'
|
||||
import Mock from 'mockjs'
|
||||
|
||||
const { code } = config
|
||||
|
||||
const departmentList: any = []
|
||||
|
||||
const citys = ['厦门总公司', '北京分公司', '上海分公司', '福州分公司', '深圳分公司', '杭州分公司']
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
departmentList.push({
|
||||
// 部门名称
|
||||
departmentName: citys[i],
|
||||
id: toAnyString(),
|
||||
createTime: '@datetime',
|
||||
// 状态
|
||||
status: Mock.Random.integer(0, 1),
|
||||
// 备注
|
||||
remark: '@cword(10, 15)',
|
||||
children: [
|
||||
{
|
||||
// 部门名称
|
||||
departmentName: '研发部',
|
||||
createTime: '@datetime',
|
||||
// 状态
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
remark: '@cword(10, 15)'
|
||||
},
|
||||
{
|
||||
// 部门名称
|
||||
departmentName: '产品部',
|
||||
createTime: '@datetime',
|
||||
// 状态
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
remark: '@cword(10, 15)'
|
||||
},
|
||||
{
|
||||
// 部门名称
|
||||
departmentName: '运营部',
|
||||
createTime: '@datetime',
|
||||
// 状态
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
remark: '@cword(10, 15)'
|
||||
},
|
||||
{
|
||||
// 部门名称
|
||||
departmentName: '市场部',
|
||||
createTime: '@datetime',
|
||||
// 状态
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
remark: '@cword(10, 15)'
|
||||
},
|
||||
{
|
||||
// 部门名称
|
||||
departmentName: '销售部',
|
||||
createTime: '@datetime',
|
||||
// 状态
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
remark: '@cword(10, 15)'
|
||||
},
|
||||
{
|
||||
// 部门名称
|
||||
departmentName: '客服部',
|
||||
createTime: '@datetime',
|
||||
// 状态
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
remark: '@cword(10, 15)'
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
export default [
|
||||
// 列表接口
|
||||
{
|
||||
url: '/department/list',
|
||||
method: 'get',
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: {
|
||||
list: departmentList
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
url: '/department/table/list',
|
||||
method: 'get',
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: {
|
||||
list: departmentList,
|
||||
total: 5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
url: '/department/users',
|
||||
method: 'get',
|
||||
timeout: 1000,
|
||||
response: ({ query }) => {
|
||||
const { pageSize } = query
|
||||
// 根据pageSize来创建数据
|
||||
const mockList: any = []
|
||||
for (let i = 0; i < pageSize; i++) {
|
||||
mockList.push(
|
||||
Mock.mock({
|
||||
// 用户名
|
||||
username: '@cname',
|
||||
// 账号
|
||||
account: '@first',
|
||||
// 邮箱
|
||||
email: '@EMAIL',
|
||||
// 创建时间
|
||||
createTime: '@datetime',
|
||||
// 角色
|
||||
role: '@first',
|
||||
// 用户id
|
||||
id: toAnyString()
|
||||
})
|
||||
)
|
||||
}
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: {
|
||||
total: 100,
|
||||
list: mockList
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 保存接口
|
||||
{
|
||||
url: '/department/user/save',
|
||||
method: 'post',
|
||||
timeout: 1000,
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: 'success'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 删除接口
|
||||
{
|
||||
url: '/department/user/delete',
|
||||
method: 'post',
|
||||
response: ({ body }) => {
|
||||
const ids = body.ids
|
||||
if (!ids) {
|
||||
return {
|
||||
code: '500',
|
||||
message: '请选择需要删除的数据'
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: 'success'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 保存接口
|
||||
{
|
||||
url: '/department/save',
|
||||
method: 'post',
|
||||
timeout: 1000,
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: 'success'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 删除接口
|
||||
{
|
||||
url: '/department/delete',
|
||||
method: 'post',
|
||||
response: ({ body }) => {
|
||||
const ids = body.ids
|
||||
if (!ids) {
|
||||
return {
|
||||
code: '500',
|
||||
message: '请选择需要删除的数据'
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: 'success'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
] as MockMethod[]
|
|
@ -1,67 +0,0 @@
|
|||
import config from '@/config/axios/config'
|
||||
import { MockMethod } from 'vite-plugin-mock'
|
||||
|
||||
const { code } = config
|
||||
|
||||
const timeout = 1000
|
||||
|
||||
const dictObj: Recordable = {
|
||||
importance: [
|
||||
{
|
||||
value: 0,
|
||||
label: 'tableDemo.commonly'
|
||||
},
|
||||
{
|
||||
value: 1,
|
||||
label: 'tableDemo.good'
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: 'tableDemo.important'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export default [
|
||||
// 字典接口
|
||||
{
|
||||
url: '/dict/list',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: dictObj
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 获取某个字典
|
||||
{
|
||||
url: '/dict/one',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: [
|
||||
{
|
||||
label: 'test1',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
label: 'test2',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: 'test3',
|
||||
value: 2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
] as MockMethod[]
|
|
@ -1,246 +0,0 @@
|
|||
import config from '@/config/axios/config'
|
||||
import { MockMethod } from 'vite-plugin-mock'
|
||||
import Mock from 'mockjs'
|
||||
import { toAnyString } from '@/utils'
|
||||
|
||||
const { code } = config
|
||||
|
||||
const timeout = 1000
|
||||
|
||||
export default [
|
||||
// 列表接口
|
||||
{
|
||||
url: '/menu/list',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: {
|
||||
list: [
|
||||
{
|
||||
path: '/dashboard',
|
||||
component: '#',
|
||||
redirect: '/dashboard/analysis',
|
||||
name: 'Dashboard',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
meta: {
|
||||
title: '首页',
|
||||
icon: 'ant-design:dashboard-filled',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'analysis',
|
||||
component: 'views/Dashboard/Analysis',
|
||||
name: 'Analysis',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
meta: {
|
||||
title: '分析页',
|
||||
noCache: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'workplace',
|
||||
component: 'views/Dashboard/Workplace',
|
||||
name: 'Workplace',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
meta: {
|
||||
title: '工作台',
|
||||
noCache: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/external-link',
|
||||
component: '#',
|
||||
meta: {
|
||||
title: '文档',
|
||||
icon: 'clarity:document-solid'
|
||||
},
|
||||
name: 'ExternalLink',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
children: [
|
||||
{
|
||||
path: 'https://element-plus-admin-doc.cn/',
|
||||
name: 'DocumentLink',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
meta: {
|
||||
title: '文档'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/level',
|
||||
component: '#',
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
name: 'Level',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
meta: {
|
||||
title: '菜单',
|
||||
icon: 'carbon:skill-level-advanced'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1',
|
||||
name: 'Menu1',
|
||||
component: '##',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
meta: {
|
||||
title: '菜单1'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1',
|
||||
name: 'Menu11',
|
||||
component: '##',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
meta: {
|
||||
title: '菜单1-1',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1-1',
|
||||
name: 'Menu111',
|
||||
component: 'views/Level/Menu111',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
meta: {
|
||||
title: '菜单1-1-1',
|
||||
permission: ['edit', 'add']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'menu1-2',
|
||||
name: 'Menu12',
|
||||
component: 'views/Level/Menu12',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
meta: {
|
||||
title: '菜单1-2',
|
||||
permission: ['edit', 'add']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'menu2',
|
||||
name: 'Menu2Demo',
|
||||
component: 'views/Level/Menu2',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
meta: {
|
||||
title: '菜单2',
|
||||
permission: ['edit', 'add']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/example',
|
||||
component: '#',
|
||||
redirect: '/example/example-dialog',
|
||||
name: 'Example',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
meta: {
|
||||
title: '综合示例',
|
||||
icon: 'ep:management',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'example-dialog',
|
||||
component: 'views/Example/Dialog/ExampleDialog',
|
||||
name: 'ExampleDialog',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
meta: {
|
||||
title: '综合示例-弹窗',
|
||||
permission: ['edit', 'add', 'delete']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-page',
|
||||
component: 'views/Example/Page/ExamplePage',
|
||||
name: 'ExamplePage',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
meta: {
|
||||
title: '综合示例-页面',
|
||||
permission: ['edit', 'add', 'delete']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-add',
|
||||
component: 'views/Example/Page/ExampleAdd',
|
||||
name: 'ExampleAdd',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
meta: {
|
||||
title: '综合示例-新增',
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
showMainRoute: true,
|
||||
activeMenu: '/example/example-page',
|
||||
permission: ['edit', 'add', 'delete']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-edit',
|
||||
component: 'views/Example/Page/ExampleEdit',
|
||||
name: 'ExampleEdit',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
meta: {
|
||||
title: '综合示例-编辑',
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
showMainRoute: true,
|
||||
activeMenu: '/example/example-page',
|
||||
permission: ['edit', 'add', 'delete']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-detail',
|
||||
component: 'views/Example/Page/ExampleDetail',
|
||||
name: 'ExampleDetail',
|
||||
status: Mock.Random.integer(0, 1),
|
||||
id: toAnyString(),
|
||||
meta: {
|
||||
title: '综合示例-详情',
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
showMainRoute: true,
|
||||
activeMenu: '/example/example-page',
|
||||
permission: ['edit', 'add', 'delete']
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
] as MockMethod[]
|
|
@ -0,0 +1,91 @@
|
|||
import { MockMethod } from 'vite-plugin-mock'
|
||||
import { SUCCESS_CODE } from '@/constants'
|
||||
|
||||
const timeout = 1000
|
||||
|
||||
const adminList = [
|
||||
{
|
||||
path: '/level',
|
||||
component: '#',
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
name: 'Level',
|
||||
meta: {
|
||||
title: 'router.level',
|
||||
icon: 'carbon:skill-level-advanced'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1',
|
||||
name: 'Menu1',
|
||||
component: '##',
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
meta: {
|
||||
title: 'router.menu1'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1',
|
||||
name: 'Menu11',
|
||||
component: '##',
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
meta: {
|
||||
title: 'router.menu11',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1-1',
|
||||
name: 'Menu111',
|
||||
component: 'views/Level/Menu111',
|
||||
meta: {
|
||||
title: 'router.menu111'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'menu1-2',
|
||||
name: 'Menu12',
|
||||
component: 'views/Level/Menu12',
|
||||
meta: {
|
||||
title: 'router.menu12'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'menu2',
|
||||
name: 'Menu2Demo',
|
||||
component: 'views/Level/Menu2',
|
||||
meta: {
|
||||
title: 'router.menu2'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const testList: string[] = [
|
||||
'/level',
|
||||
'/level/menu1',
|
||||
'/level/menu1/menu1-1',
|
||||
'/level/menu1/menu1-1/menu1-1-1',
|
||||
'/level/menu1/menu1-2',
|
||||
'/level/menu2'
|
||||
]
|
||||
|
||||
export default [
|
||||
// 列表接口
|
||||
{
|
||||
url: '/mock/role/list',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: ({ query }) => {
|
||||
const { roleName } = query
|
||||
return {
|
||||
code: SUCCESS_CODE,
|
||||
data: roleName === 'admin' ? adminList : testList
|
||||
}
|
||||
}
|
||||
}
|
||||
] as MockMethod[]
|
|
@ -1,546 +0,0 @@
|
|||
import config from '@/config/axios/config'
|
||||
import { MockMethod } from 'vite-plugin-mock'
|
||||
|
||||
const { code } = config
|
||||
|
||||
const timeout = 1000
|
||||
|
||||
const adminList = [
|
||||
{
|
||||
path: '/dashboard',
|
||||
component: '#',
|
||||
redirect: '/dashboard/analysis',
|
||||
name: 'Dashboard',
|
||||
meta: {
|
||||
title: 'router.dashboard',
|
||||
icon: 'ant-design:dashboard-filled',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'analysis',
|
||||
component: 'views/Dashboard/Analysis',
|
||||
name: 'Analysis',
|
||||
meta: {
|
||||
title: 'router.analysis',
|
||||
noCache: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'workplace',
|
||||
component: 'views/Dashboard/Workplace',
|
||||
name: 'Workplace',
|
||||
meta: {
|
||||
title: 'router.workplace',
|
||||
noCache: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/external-link',
|
||||
component: '#',
|
||||
meta: {},
|
||||
name: 'ExternalLink',
|
||||
children: [
|
||||
{
|
||||
path: 'https://element-plus-admin-doc.cn/',
|
||||
name: 'DocumentLink',
|
||||
meta: {
|
||||
title: 'router.document',
|
||||
icon: 'clarity:document-solid'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/guide',
|
||||
component: '#',
|
||||
name: 'Guide',
|
||||
meta: {},
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
component: 'views/Guide/Guide',
|
||||
name: 'GuideDemo',
|
||||
meta: {
|
||||
title: 'router.guide',
|
||||
icon: 'cib:telegram-plane'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/components',
|
||||
component: '#',
|
||||
redirect: '/components/form/default-form',
|
||||
name: 'ComponentsDemo',
|
||||
meta: {
|
||||
title: 'router.component',
|
||||
icon: 'bx:bxs-component',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'form',
|
||||
component: '##',
|
||||
name: 'Form',
|
||||
meta: {
|
||||
title: 'router.form',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'default-form',
|
||||
component: 'views/Components/Form/DefaultForm',
|
||||
name: 'DefaultForm',
|
||||
meta: {
|
||||
title: 'router.defaultForm'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'use-form',
|
||||
component: 'views/Components/Form/UseFormDemo',
|
||||
name: 'UseForm',
|
||||
meta: {
|
||||
title: 'UseForm'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'table',
|
||||
component: '##',
|
||||
redirect: '/components/table/default-table',
|
||||
name: 'TableDemo',
|
||||
meta: {
|
||||
title: 'router.table',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'default-table',
|
||||
component: 'views/Components/Table/DefaultTable',
|
||||
name: 'DefaultTable',
|
||||
meta: {
|
||||
title: 'router.defaultTable'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'use-table',
|
||||
component: 'views/Components/Table/UseTableDemo',
|
||||
name: 'UseTable',
|
||||
meta: {
|
||||
title: 'UseTable'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'tree-table',
|
||||
component: 'views/Components/Table/TreeTable',
|
||||
name: 'TreeTable',
|
||||
meta: {
|
||||
title: 'router.TreeTable'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'table-image-preview',
|
||||
component: 'views/Components/Table/TableImagePreview',
|
||||
name: 'TableImagePreview',
|
||||
meta: {
|
||||
title: 'router.PicturePreview'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'ref-table',
|
||||
component: 'views/Components/Table/RefTable',
|
||||
name: 'RefTable',
|
||||
meta: {
|
||||
title: 'RefTable'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'editor-demo',
|
||||
component: '##',
|
||||
redirect: '/components/editor-demo/editor',
|
||||
name: 'EditorDemo',
|
||||
meta: {
|
||||
title: 'router.editor',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'editor',
|
||||
component: 'views/Components/Editor/Editor',
|
||||
name: 'Editor',
|
||||
meta: {
|
||||
title: 'router.richText'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'search',
|
||||
component: 'views/Components/Search',
|
||||
name: 'Search',
|
||||
meta: {
|
||||
title: 'router.search'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'descriptions',
|
||||
component: 'views/Components/Descriptions',
|
||||
name: 'Descriptions',
|
||||
meta: {
|
||||
title: 'router.descriptions'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'image-viewer',
|
||||
component: 'views/Components/ImageViewer',
|
||||
name: 'ImageViewer',
|
||||
meta: {
|
||||
title: 'router.imageViewer'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'dialog',
|
||||
component: 'views/Components/Dialog',
|
||||
name: 'Dialog',
|
||||
meta: {
|
||||
title: 'router.dialog'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'icon',
|
||||
component: 'views/Components/Icon',
|
||||
name: 'Icon',
|
||||
meta: {
|
||||
title: 'router.icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'echart',
|
||||
component: 'views/Components/Echart',
|
||||
name: 'Echart',
|
||||
meta: {
|
||||
title: 'router.echart'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'count-to',
|
||||
component: 'views/Components/CountTo',
|
||||
name: 'CountTo',
|
||||
meta: {
|
||||
title: 'router.countTo'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'qrcode',
|
||||
component: 'views/Components/Qrcode',
|
||||
name: 'Qrcode',
|
||||
meta: {
|
||||
title: 'router.qrcode'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'highlight',
|
||||
component: 'views/Components/Highlight',
|
||||
name: 'Highlight',
|
||||
meta: {
|
||||
title: 'router.highlight'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'infotip',
|
||||
component: 'views/Components/Infotip',
|
||||
name: 'Infotip',
|
||||
meta: {
|
||||
title: 'router.infotip'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'input-password',
|
||||
component: 'views/Components/InputPassword',
|
||||
name: 'InputPassword',
|
||||
meta: {
|
||||
title: 'router.inputPassword'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'sticky',
|
||||
component: 'views/Components/Sticky',
|
||||
name: 'Sticky',
|
||||
meta: {
|
||||
title: 'router.sticky'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/hooks',
|
||||
component: '#',
|
||||
redirect: '/hooks/useWatermark',
|
||||
name: 'Hooks',
|
||||
meta: {
|
||||
title: 'hooks',
|
||||
icon: 'ic:outline-webhook',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'useWatermark',
|
||||
component: 'views/hooks/useWatermark',
|
||||
name: 'UseWatermark',
|
||||
meta: {
|
||||
title: 'useWatermark'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'useCrudSchemas',
|
||||
component: 'views/hooks/useCrudSchemas',
|
||||
name: 'UseCrudSchemas',
|
||||
meta: {
|
||||
title: 'useCrudSchemas'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/level',
|
||||
component: '#',
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
name: 'Level',
|
||||
meta: {
|
||||
title: 'router.level',
|
||||
icon: 'carbon:skill-level-advanced'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1',
|
||||
name: 'Menu1',
|
||||
component: '##',
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
meta: {
|
||||
title: 'router.menu1'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1',
|
||||
name: 'Menu11',
|
||||
component: '##',
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
meta: {
|
||||
title: 'router.menu11',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1-1',
|
||||
name: 'Menu111',
|
||||
component: 'views/Level/Menu111',
|
||||
meta: {
|
||||
title: 'router.menu111'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'menu1-2',
|
||||
name: 'Menu12',
|
||||
component: 'views/Level/Menu12',
|
||||
meta: {
|
||||
title: 'router.menu12'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'menu2',
|
||||
name: 'Menu2Demo',
|
||||
component: 'views/Level/Menu2',
|
||||
meta: {
|
||||
title: 'router.menu2'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/example',
|
||||
component: '#',
|
||||
redirect: '/example/example-dialog',
|
||||
name: 'Example',
|
||||
meta: {
|
||||
title: 'router.example',
|
||||
icon: 'ep:management',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'example-dialog',
|
||||
component: 'views/Example/Dialog/ExampleDialog',
|
||||
name: 'ExampleDialog',
|
||||
meta: {
|
||||
title: 'router.exampleDialog'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-page',
|
||||
component: 'views/Example/Page/ExamplePage',
|
||||
name: 'ExamplePage',
|
||||
meta: {
|
||||
title: 'router.examplePage'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-add',
|
||||
component: 'views/Example/Page/ExampleAdd',
|
||||
name: 'ExampleAdd',
|
||||
meta: {
|
||||
title: 'router.exampleAdd',
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
showMainRoute: true,
|
||||
activeMenu: '/example/example-page'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-edit',
|
||||
component: 'views/Example/Page/ExampleEdit',
|
||||
name: 'ExampleEdit',
|
||||
meta: {
|
||||
title: 'router.exampleEdit',
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
showMainRoute: true,
|
||||
activeMenu: '/example/example-page'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-detail',
|
||||
component: 'views/Example/Page/ExampleDetail',
|
||||
name: 'ExampleDetail',
|
||||
meta: {
|
||||
title: 'router.exampleDetail',
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
showMainRoute: true,
|
||||
activeMenu: '/example/example-page'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/error',
|
||||
component: '#',
|
||||
redirect: '/error/404',
|
||||
name: 'Error',
|
||||
meta: {
|
||||
title: 'router.errorPage',
|
||||
icon: 'ci:error',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '404-demo',
|
||||
component: 'views/Error/404',
|
||||
name: '404Demo',
|
||||
meta: {
|
||||
title: '404'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '403-demo',
|
||||
component: 'views/Error/403',
|
||||
name: '403Demo',
|
||||
meta: {
|
||||
title: '403'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '500-demo',
|
||||
component: 'views/Error/500',
|
||||
name: '500Demo',
|
||||
meta: {
|
||||
title: '500'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const testList: string[] = [
|
||||
'/dashboard',
|
||||
'/dashboard/analysis',
|
||||
'/dashboard/workplace',
|
||||
'external-link',
|
||||
'https://element-plus-admin-doc.cn/',
|
||||
'/guide',
|
||||
'/guide/index',
|
||||
'/components',
|
||||
'/components/form',
|
||||
'/components/form/default-form',
|
||||
'/components/form/use-form',
|
||||
'/components/form/ref-form',
|
||||
'/components/table',
|
||||
'/components/table/default-table',
|
||||
'/components/table/use-table',
|
||||
'/components/table/tree-table',
|
||||
'/components/table/table-image-preview',
|
||||
'/components/table/ref-table',
|
||||
'/components/editor-demo',
|
||||
'/components/editor-demo/editor',
|
||||
'/components/search',
|
||||
'/components/descriptions',
|
||||
'/components/image-viewer',
|
||||
'/components/dialog',
|
||||
'/components/icon',
|
||||
'/components/echart',
|
||||
'/components/count-to',
|
||||
'/components/qrcode',
|
||||
'/components/highlight',
|
||||
'/components/infotip',
|
||||
'/Components/InputPassword',
|
||||
'/Components/Sticky',
|
||||
'/hooks',
|
||||
'/hooks/useWatermark',
|
||||
'/hooks/useCrudSchemas',
|
||||
'/level',
|
||||
'/level/menu1',
|
||||
'/level/menu1/menu1-1',
|
||||
'/level/menu1/menu1-1/menu1-1-1',
|
||||
'/level/menu1/menu1-2',
|
||||
'/level/menu2',
|
||||
'/example',
|
||||
'/example/example-dialog',
|
||||
'/example/example-page',
|
||||
'/example/example-add',
|
||||
'/example/example-edit',
|
||||
'/example/example-detail',
|
||||
'/error',
|
||||
'/error/404-demo',
|
||||
'/error/403-demo',
|
||||
'/error/500-demo'
|
||||
]
|
||||
|
||||
export default [
|
||||
// 列表接口
|
||||
{
|
||||
url: '/role/list',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: ({ query }) => {
|
||||
const { roleName } = query
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: roleName === 'admin' ? adminList : testList
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
] as MockMethod[]
|
|
@ -1,268 +0,0 @@
|
|||
import config from '@/config/axios/config'
|
||||
import { MockMethod } from 'vite-plugin-mock'
|
||||
import { toAnyString } from '@/utils'
|
||||
import Mock from 'mockjs'
|
||||
|
||||
const { code } = config
|
||||
|
||||
const timeout = 1000
|
||||
|
||||
const count = 100
|
||||
|
||||
const baseContent =
|
||||
'<p>I am testing data, I am testing data.</p><p><img src="https://wpimg.wallstcn.com/4c69009c-0fd4-4153-b112-6cb53d1cf943"></p>'
|
||||
|
||||
interface ListProps {
|
||||
id: string
|
||||
author: string
|
||||
title: string
|
||||
content: string
|
||||
importance: number
|
||||
display_time: string
|
||||
pageviews: number
|
||||
image_uri: string
|
||||
}
|
||||
|
||||
interface TreeListProps {
|
||||
id: string
|
||||
author: string
|
||||
title: string
|
||||
content: string
|
||||
importance: number
|
||||
display_time: string
|
||||
pageviews: number
|
||||
children: TreeListProps[]
|
||||
}
|
||||
|
||||
let List: ListProps[] = []
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
List.push(
|
||||
Mock.mock({
|
||||
id: toAnyString(),
|
||||
// timestamp: +Mock.Random.date('T'),
|
||||
author: '@first',
|
||||
title: '@title(5, 10)',
|
||||
content: baseContent,
|
||||
importance: '@integer(1, 3)',
|
||||
display_time: '@datetime',
|
||||
pageviews: '@integer(300, 5000)',
|
||||
image_uri: Mock.Random.image('@integer(300, 5000)x@integer(300, 5000)')
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const treeList: TreeListProps[] = []
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
treeList.push(
|
||||
Mock.mock({
|
||||
id: toAnyString(),
|
||||
// timestamp: +Mock.Random.date('T'),
|
||||
author: '@first',
|
||||
title: '@title(5, 10)',
|
||||
content: baseContent,
|
||||
importance: '@integer(1, 3)',
|
||||
display_time: '@datetime',
|
||||
pageviews: '@integer(300, 5000)',
|
||||
children: [
|
||||
{
|
||||
id: toAnyString(),
|
||||
// timestamp: +Mock.Random.date('T'),
|
||||
author: '@first',
|
||||
title: '@title(5, 10)',
|
||||
content: baseContent,
|
||||
importance: '@integer(1, 3)',
|
||||
display_time: '@datetime',
|
||||
pageviews: '@integer(300, 5000)',
|
||||
children: [
|
||||
{
|
||||
id: toAnyString(),
|
||||
// timestamp: +Mock.Random.date('T'),
|
||||
author: '@first',
|
||||
title: '@title(5, 10)',
|
||||
content: baseContent,
|
||||
importance: '@integer(1, 3)',
|
||||
display_time: '@datetime',
|
||||
pageviews: '@integer(300, 5000)'
|
||||
},
|
||||
{
|
||||
id: toAnyString(),
|
||||
// timestamp: +Mock.Random.date('T'),
|
||||
author: '@first',
|
||||
title: '@title(5, 10)',
|
||||
content: baseContent,
|
||||
importance: '@integer(1, 3)',
|
||||
display_time: '@datetime',
|
||||
pageviews: '@integer(300, 5000)'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: toAnyString(),
|
||||
// timestamp: +Mock.Random.date('T'),
|
||||
author: '@first',
|
||||
title: '@title(5, 10)',
|
||||
content: baseContent,
|
||||
importance: '@integer(1, 3)',
|
||||
display_time: '@datetime',
|
||||
pageviews: '@integer(300, 5000)'
|
||||
},
|
||||
{
|
||||
id: toAnyString(),
|
||||
// timestamp: +Mock.Random.date('T'),
|
||||
author: '@first',
|
||||
title: '@title(5, 10)',
|
||||
content: baseContent,
|
||||
importance: '@integer(1, 3)',
|
||||
display_time: '@datetime',
|
||||
pageviews: '@integer(300, 5000)'
|
||||
},
|
||||
{
|
||||
id: toAnyString(),
|
||||
// timestamp: +Mock.Random.date('T'),
|
||||
author: '@first',
|
||||
title: '@title(5, 10)',
|
||||
content: baseContent,
|
||||
importance: '@integer(1, 3)',
|
||||
display_time: '@datetime',
|
||||
pageviews: '@integer(300, 5000)'
|
||||
}
|
||||
]
|
||||
// image_uri
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export default [
|
||||
// 树形列表接口
|
||||
{
|
||||
url: '/example/treeList',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: ({ query }) => {
|
||||
const { title, pageIndex, pageSize } = query
|
||||
const mockList = treeList.filter((item) => {
|
||||
if (title && item.title.indexOf(title) < 0) return false
|
||||
return true
|
||||
})
|
||||
const pageList = mockList.filter(
|
||||
(_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1)
|
||||
)
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: {
|
||||
total: mockList.length,
|
||||
list: pageList
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 列表接口
|
||||
{
|
||||
url: '/example/list',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: ({ query }) => {
|
||||
const { title, pageIndex, pageSize } = query
|
||||
const mockList = List.filter((item) => {
|
||||
if (title && item.title.indexOf(title) < 0) return false
|
||||
return true
|
||||
})
|
||||
const pageList = mockList.filter(
|
||||
(_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1)
|
||||
)
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: {
|
||||
total: mockList.length,
|
||||
list: pageList
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 保存接口
|
||||
{
|
||||
url: '/example/save',
|
||||
method: 'post',
|
||||
timeout,
|
||||
response: ({ body }) => {
|
||||
if (!body.id) {
|
||||
List = [
|
||||
Object.assign(body, {
|
||||
id: toAnyString()
|
||||
})
|
||||
].concat(List)
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: 'success'
|
||||
}
|
||||
}
|
||||
} else {
|
||||
List.map((item) => {
|
||||
if (item.id === body.id) {
|
||||
for (const key in item) {
|
||||
item[key] = body[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: 'success'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 详情接口
|
||||
{
|
||||
url: '/example/detail',
|
||||
method: 'get',
|
||||
response: ({ query }) => {
|
||||
const { id } = query
|
||||
for (const example of List) {
|
||||
if (example.id === id) {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: example
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 删除接口
|
||||
{
|
||||
url: '/example/delete',
|
||||
method: 'post',
|
||||
response: ({ body }) => {
|
||||
const ids = body.ids
|
||||
if (!ids) {
|
||||
return {
|
||||
code: '500',
|
||||
message: '请选择需要删除的数据'
|
||||
}
|
||||
} else {
|
||||
let i = List.length
|
||||
while (i--) {
|
||||
if (ids.indexOf(List[i].id) !== -1) {
|
||||
List.splice(i, 1)
|
||||
}
|
||||
}
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: 'success'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
] as MockMethod[]
|
|
@ -1,7 +1,4 @@
|
|||
import config from '@/config/axios/config'
|
||||
import { MockMethod } from 'vite-plugin-mock'
|
||||
|
||||
const { code } = config
|
||||
import { SUCCESS_CODE } from '@/constants'
|
||||
|
||||
const timeout = 1000
|
||||
|
||||
|
@ -31,7 +28,7 @@ const List: {
|
|||
export default [
|
||||
// 列表接口
|
||||
{
|
||||
url: '/user/list',
|
||||
url: '/mock/user/list',
|
||||
method: 'get',
|
||||
response: ({ query }) => {
|
||||
const { username, pageIndex, pageSize } = query
|
||||
|
@ -45,19 +42,17 @@ export default [
|
|||
)
|
||||
|
||||
return {
|
||||
code: SUCCESS_CODE,
|
||||
data: {
|
||||
code: code,
|
||||
data: {
|
||||
total: mockList.length,
|
||||
list: pageList
|
||||
}
|
||||
total: mockList.length,
|
||||
list: pageList
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 登录接口
|
||||
{
|
||||
url: '/user/login',
|
||||
url: '/mock/user/login',
|
||||
method: 'post',
|
||||
timeout,
|
||||
response: ({ body }) => {
|
||||
|
@ -67,10 +62,8 @@ export default [
|
|||
if (user.username === data.username && user.password === data.password) {
|
||||
hasUser = true
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: user
|
||||
}
|
||||
code: SUCCESS_CODE,
|
||||
data: user
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -84,16 +77,14 @@ export default [
|
|||
},
|
||||
// 退出接口
|
||||
{
|
||||
url: '/user/loginOut',
|
||||
url: '/mock/user/loginOut',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: null
|
||||
}
|
||||
code: SUCCESS_CODE,
|
||||
data: null
|
||||
}
|
||||
}
|
||||
}
|
||||
] as MockMethod[]
|
||||
]
|
|
@ -1,182 +0,0 @@
|
|||
import config from '@/config/axios/config'
|
||||
import { MockMethod } from 'vite-plugin-mock'
|
||||
|
||||
const { code } = config
|
||||
|
||||
const timeout = 1000
|
||||
|
||||
export default [
|
||||
// 获取统计
|
||||
{
|
||||
url: '/workplace/total',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: {
|
||||
project: 40,
|
||||
access: 2340,
|
||||
todo: 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 获取项目
|
||||
{
|
||||
url: '/workplace/project',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: [
|
||||
{
|
||||
name: 'Github',
|
||||
icon: 'akar-icons:github-fill',
|
||||
message: 'workplace.introduction',
|
||||
personal: 'Archer',
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
name: 'Vue',
|
||||
icon: 'logos:vue',
|
||||
message: 'workplace.introduction',
|
||||
personal: 'Archer',
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
name: 'Angular',
|
||||
icon: 'logos:angular-icon',
|
||||
message: 'workplace.introduction',
|
||||
personal: 'Archer',
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
name: 'React',
|
||||
icon: 'logos:react',
|
||||
message: 'workplace.introduction',
|
||||
personal: 'Archer',
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
name: 'Webpack',
|
||||
icon: 'logos:webpack',
|
||||
message: 'workplace.introduction',
|
||||
personal: 'Archer',
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
name: 'Vite',
|
||||
icon: 'vscode-icons:file-type-vite',
|
||||
message: 'workplace.introduction',
|
||||
personal: 'Archer',
|
||||
time: new Date()
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 获取动态
|
||||
{
|
||||
url: '/workplace/dynamic',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: [
|
||||
{
|
||||
keys: ['workplace.push', 'Github'],
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
keys: ['workplace.push', 'Github'],
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
keys: ['workplace.push', 'Github'],
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
keys: ['workplace.push', 'Github'],
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
keys: ['workplace.push', 'Github'],
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
keys: ['workplace.push', 'Github'],
|
||||
time: new Date()
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 获取团队信息
|
||||
{
|
||||
url: '/workplace/team',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: [
|
||||
{
|
||||
name: 'Github',
|
||||
icon: 'akar-icons:github-fill'
|
||||
},
|
||||
{
|
||||
name: 'Vue',
|
||||
icon: 'logos:vue'
|
||||
},
|
||||
{
|
||||
name: 'Angular',
|
||||
icon: 'logos:angular-icon'
|
||||
},
|
||||
{
|
||||
name: 'React',
|
||||
icon: 'logos:react'
|
||||
},
|
||||
{
|
||||
name: 'Webpack',
|
||||
icon: 'logos:webpack'
|
||||
},
|
||||
{
|
||||
name: 'Vite',
|
||||
icon: 'vscode-icons:file-type-vite'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 获取指数
|
||||
{
|
||||
url: '/workplace/radar',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: [
|
||||
{ name: 'workplace.quote', max: 65, personal: 42, team: 50 },
|
||||
{ name: 'workplace.contribution', max: 160, personal: 30, team: 140 },
|
||||
{ name: 'workplace.hot', max: 300, personal: 20, team: 28 },
|
||||
{ name: 'workplace.yield', max: 130, personal: 35, team: 35 },
|
||||
{ name: 'workplace.follow', max: 100, personal: 80, team: 90 }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
] as MockMethod[]
|
159
package.json
159
package.json
|
@ -1,23 +1,23 @@
|
|||
{
|
||||
"name": "vue-element-plus-admin",
|
||||
"version": "1.9.9",
|
||||
"version": "2.5.6",
|
||||
"description": "一套基于vue3、element-plus、typesScript、vite4的后台集成方案。",
|
||||
"author": "Archer <502431556@qq.com>",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"i": "pnpm install",
|
||||
"dev": "vite --mode base",
|
||||
"ts:check": "vue-tsc --noEmit --skipLibCheck",
|
||||
"build:pro": "vite build --mode pro",
|
||||
"build:gitee": "vite build --mode gitee",
|
||||
"build:dev": "vite build --mode dev",
|
||||
"build:test": "npm run ts:check && vite build --mode test",
|
||||
"serve:pro": "vite preview --mode pro",
|
||||
"serve:dev": "vite preview --mode dev",
|
||||
"serve:test": "vite preview --mode test",
|
||||
"npm:check": "npx npm-check-updates",
|
||||
"clean": "npx rimraf node_modules",
|
||||
"clean:cache": "npx rimraf node_modules/.cache",
|
||||
"dev": "pnpm vite --mode base",
|
||||
"ts:check": "pnpm vue-tsc --noEmit --skipLibCheck",
|
||||
"build:pro": "pnpm vite build --mode pro",
|
||||
"build:gitee": "pnpm vite build --mode gitee",
|
||||
"build:dev": "pnpm vite build --mode dev",
|
||||
"build:test": "pnpm vite build --mode test",
|
||||
"serve:pro": "pnpm vite preview --mode pro",
|
||||
"serve:dev": "pnpm vite preview --mode dev",
|
||||
"serve:test": "pnpm vite preview --mode test",
|
||||
"npm:check": "pnpx npm-check-updates -u",
|
||||
"clean": "pnpx rimraf node_modules",
|
||||
"clean:cache": "pnpx rimraf node_modules/.cache",
|
||||
"lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
|
||||
"lint:format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,vue,html,md}\"",
|
||||
"lint:style": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
|
||||
|
@ -26,93 +26,100 @@
|
|||
"p": "plop"
|
||||
},
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "^8.4.0",
|
||||
"@iconify/iconify": "^3.1.1",
|
||||
"@iconify/vue": "^4.1.1",
|
||||
"@vueuse/core": "^10.2.1",
|
||||
"@vueuse/core": "^10.7.2",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||
"@zxcvbn-ts/core": "^3.0.3",
|
||||
"@zxcvbn-ts/core": "^3.0.4",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.4.0",
|
||||
"dayjs": "^1.11.9",
|
||||
"axios": "^1.6.7",
|
||||
"cropperjs": "^1.6.1",
|
||||
"dayjs": "^1.11.10",
|
||||
"driver.js": "^1.3.1",
|
||||
"echarts": "^5.4.3",
|
||||
"echarts-wordcloud": "^2.1.0",
|
||||
"element-plus": "^2.3.8",
|
||||
"intro.js": "^7.0.1",
|
||||
"element-plus": "2.5.5",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mitt": "^3.0.1",
|
||||
"mockjs": "^1.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.4",
|
||||
"pinia-plugin-persist": "^1.0.0",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"qrcode": "^1.5.3",
|
||||
"qs": "^6.11.2",
|
||||
"sortablejs": "^1.15.0",
|
||||
"url": "^0.11.1",
|
||||
"vue": "3.3.4",
|
||||
"vue-i18n": "9.2.2",
|
||||
"vue-router": "^4.2.4",
|
||||
"vue-types": "^5.1.0"
|
||||
"url": "^0.11.3",
|
||||
"vue": "3.4.15",
|
||||
"vue-draggable-plus": "^0.3.5",
|
||||
"vue-i18n": "9.9.1",
|
||||
"vue-json-pretty": "^2.3.0",
|
||||
"vue-router": "^4.2.5",
|
||||
"vue-types": "^5.1.1",
|
||||
"xgplayer": "^3.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.6.7",
|
||||
"@commitlint/config-conventional": "^17.6.7",
|
||||
"@iconify/json": "^2.2.92",
|
||||
"@intlify/unplugin-vue-i18n": "^0.12.2",
|
||||
"@purge-icons/generated": "^0.9.0",
|
||||
"@types/intro.js": "^5.1.1",
|
||||
"@types/lodash-es": "^4.17.8",
|
||||
"@types/node": "^20.4.2",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/qrcode": "^1.5.1",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@types/sortablejs": "^1.15.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
||||
"@typescript-eslint/parser": "^6.1.0",
|
||||
"@unocss/transformer-variant-group": "^0.53.5",
|
||||
"@vitejs/plugin-legacy": "^4.1.0",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
||||
"@vue-macros/volar": "^0.12.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"@commitlint/cli": "^18.6.0",
|
||||
"@commitlint/config-conventional": "^18.6.0",
|
||||
"@iconify/json": "^2.2.180",
|
||||
"@intlify/unplugin-vue-i18n": "^2.0.0",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/inquirer": "^9.0.7",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^20.11.16",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/qs": "^6.9.11",
|
||||
"@types/sortablejs": "^1.15.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"@unocss/transformer-variant-group": "^0.58.5",
|
||||
"@vitejs/plugin-legacy": "^5.3.0",
|
||||
"@vitejs/plugin-vue": "^5.0.3",
|
||||
"@vitejs/plugin-vue-jsx": "^3.1.0",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"chalk": "^5.3.0",
|
||||
"consola": "^3.2.3",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-define-config": "^1.21.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-vue": "^9.15.1",
|
||||
"husky": "^8.0.3",
|
||||
"less": "^4.1.3",
|
||||
"lint-staged": "^13.2.3",
|
||||
"plop": "^3.1.2",
|
||||
"postcss": "^8.4.26",
|
||||
"postcss-html": "^1.5.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-define-config": "^2.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-vue": "^9.21.1",
|
||||
"esno": "^4.0.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"husky": "^9.0.10",
|
||||
"inquirer": "^9.2.14",
|
||||
"less": "^4.2.0",
|
||||
"lint-staged": "^15.2.2",
|
||||
"plop": "^4.0.1",
|
||||
"postcss": "^8.4.34",
|
||||
"postcss-html": "^1.6.0",
|
||||
"postcss-less": "^6.0.0",
|
||||
"prettier": "^3.0.0",
|
||||
"rimraf": "^5.0.1",
|
||||
"rollup": "^3.26.3",
|
||||
"stylelint": "^15.10.1",
|
||||
"prettier": "^3.2.5",
|
||||
"rimraf": "^5.0.5",
|
||||
"rollup": "^4.9.6",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"stylelint": "^16.2.1",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
"stylelint-config-prettier": "^9.0.5",
|
||||
"stylelint-config-recommended": "^13.0.0",
|
||||
"stylelint-config-standard": "^34.0.0",
|
||||
"stylelint-order": "^6.0.3",
|
||||
"terser": "^5.19.1",
|
||||
"typescript": "5.1.6",
|
||||
"unocss": "^0.53.5",
|
||||
"unplugin-vue-define-options": "^1.3.11",
|
||||
"vite": "4.4.4",
|
||||
"vite-plugin-ejs": "^1.6.4",
|
||||
"stylelint-config-recommended": "^14.0.0",
|
||||
"stylelint-config-standard": "^36.0.0",
|
||||
"stylelint-order": "^6.0.4",
|
||||
"terser": "^5.27.0",
|
||||
"typescript": "5.3.3",
|
||||
"unocss": "^0.58.5",
|
||||
"vite": "5.0.12",
|
||||
"vite-plugin-ejs": "^1.7.0",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-mock": "2.9.6",
|
||||
"vite-plugin-progress": "^0.0.7",
|
||||
"vite-plugin-purge-icons": "^0.9.2",
|
||||
"vite-plugin-purge-icons": "^0.10.0",
|
||||
"vite-plugin-style-import": "2.0.0",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vue-tsc": "^1.8.5"
|
||||
"vue-tsc": "^1.8.27"
|
||||
},
|
||||
"packageManager": "pnpm@8.1.0",
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
"node": ">=18.0.0",
|
||||
"pnpm": ">=8.1.0"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
|
16
src/App.vue
16
src/App.vue
|
@ -2,9 +2,7 @@
|
|||
import { computed } from 'vue'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { ConfigGlobal } from '@/components/ConfigGlobal'
|
||||
import { isDark } from '@/utils/is'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
|
@ -16,19 +14,7 @@ const currentSize = computed(() => appStore.getCurrentSize)
|
|||
|
||||
const greyMode = computed(() => appStore.getGreyMode)
|
||||
|
||||
const { getStorage } = useStorage()
|
||||
|
||||
// 根据浏览器当前主题设置系统主题色
|
||||
const setDefaultTheme = () => {
|
||||
if (getStorage('isDark') !== null) {
|
||||
appStore.setIsDark(getStorage('isDark'))
|
||||
return
|
||||
}
|
||||
const isDarkTheme = isDark()
|
||||
appStore.setIsDark(isDarkTheme)
|
||||
}
|
||||
|
||||
setDefaultTheme()
|
||||
appStore.initTheme()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
// 获取所有字典
|
||||
export const getDictApi = () => {
|
||||
return request.get({ url: '/dict/list' })
|
||||
}
|
||||
|
||||
// 模拟获取某个字典
|
||||
export const getDictOneApi = async () => {
|
||||
return request.get({ url: '/dict/one' })
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
import request from '@/config/axios'
|
||||
import type {
|
||||
AnalysisTotalTypes,
|
||||
UserAccessSource,
|
||||
WeeklyUserActivity,
|
||||
MonthlySales
|
||||
} from './types'
|
||||
|
||||
export const getCountApi = (): Promise<IResponse<AnalysisTotalTypes[]>> => {
|
||||
return request.get({ url: '/analysis/total' })
|
||||
}
|
||||
|
||||
export const getUserAccessSourceApi = (): Promise<IResponse<UserAccessSource[]>> => {
|
||||
return request.get({ url: '/analysis/userAccessSource' })
|
||||
}
|
||||
|
||||
export const getWeeklyUserActivityApi = (): Promise<IResponse<WeeklyUserActivity[]>> => {
|
||||
return request.get({ url: '/analysis/weeklyUserActivity' })
|
||||
}
|
||||
|
||||
export const getMonthlySalesApi = (): Promise<IResponse<MonthlySales[]>> => {
|
||||
return request.get({ url: '/analysis/monthlySales' })
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
export type AnalysisTotalTypes = {
|
||||
users: number
|
||||
messages: number
|
||||
moneys: number
|
||||
shoppings: number
|
||||
}
|
||||
|
||||
export type UserAccessSource = {
|
||||
value: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export type WeeklyUserActivity = {
|
||||
value: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export type MonthlySales = {
|
||||
name: string
|
||||
estimate: number
|
||||
actual: number
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import request from '@/config/axios'
|
||||
import type { WorkplaceTotal, Project, Dynamic, Team, RadarData } from './types'
|
||||
|
||||
export const getCountApi = (): Promise<IResponse<WorkplaceTotal>> => {
|
||||
return request.get({ url: '/workplace/total' })
|
||||
}
|
||||
|
||||
export const getProjectApi = (): Promise<IResponse<Project>> => {
|
||||
return request.get({ url: '/workplace/project' })
|
||||
}
|
||||
|
||||
export const getDynamicApi = (): Promise<IResponse<Dynamic[]>> => {
|
||||
return request.get({ url: '/workplace/dynamic' })
|
||||
}
|
||||
|
||||
export const getTeamApi = (): Promise<IResponse<Team[]>> => {
|
||||
return request.get({ url: '/workplace/team' })
|
||||
}
|
||||
|
||||
export const getRadarApi = (): Promise<IResponse<RadarData[]>> => {
|
||||
return request.get({ url: '/workplace/radar' })
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
export type WorkplaceTotal = {
|
||||
project: number
|
||||
access: number
|
||||
todo: number
|
||||
}
|
||||
|
||||
export type Project = {
|
||||
name: string
|
||||
icon: string
|
||||
message: string
|
||||
personal: string
|
||||
time: Date | number | string
|
||||
}
|
||||
|
||||
export type Dynamic = {
|
||||
keys: string[]
|
||||
time: Date | number | string
|
||||
}
|
||||
|
||||
export type Team = {
|
||||
name: string
|
||||
icon: string
|
||||
}
|
||||
|
||||
export type RadarData = {
|
||||
personal: number
|
||||
team: number
|
||||
max: number
|
||||
name: string
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
import request from '@/config/axios'
|
||||
import { DepartmentListResponse, DepartmentUserParams, DepartmentUserResponse } from './types'
|
||||
|
||||
export const getDepartmentApi = () => {
|
||||
return request.get<DepartmentListResponse>({ url: '/department/list' })
|
||||
}
|
||||
|
||||
export const getUserByIdApi = (params: DepartmentUserParams) => {
|
||||
return request.get<DepartmentUserResponse>({ url: '/department/users', params })
|
||||
}
|
||||
|
||||
export const deleteUserByIdApi = (ids: string[] | number[]) => {
|
||||
return request.post({ url: '/department/user/delete', data: { ids } })
|
||||
}
|
||||
|
||||
export const saveUserApi = (data: any) => {
|
||||
return request.post({ url: '/department/user/save', data })
|
||||
}
|
||||
|
||||
export const saveDepartmentApi = (data: any) => {
|
||||
return request.post({ url: '/department/save', data })
|
||||
}
|
||||
|
||||
export const deleteDepartmentApi = (ids: string[] | number[]) => {
|
||||
return request.post({ url: '/department/delete', data: { ids } })
|
||||
}
|
||||
|
||||
export const getDepartmentTableApi = (params: any) => {
|
||||
return request.get({ url: '/department/table/list', params })
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
export interface DepartmentItem {
|
||||
id: string
|
||||
departmentName: string
|
||||
children?: DepartmentItem[]
|
||||
}
|
||||
|
||||
export interface DepartmentListResponse {
|
||||
list: DepartmentItem[]
|
||||
}
|
||||
|
||||
export interface DepartmentUserParams {
|
||||
pageSize: number
|
||||
pageIndex: number
|
||||
id: string
|
||||
username?: string
|
||||
account?: string
|
||||
}
|
||||
|
||||
export interface DepartmentUserItem {
|
||||
id: string
|
||||
username: string
|
||||
account: string
|
||||
email: string
|
||||
createTime: string
|
||||
role: string
|
||||
department: DepartmentItem
|
||||
}
|
||||
|
||||
export interface DepartmentUserResponse {
|
||||
list: DepartmentUserItem[]
|
||||
total: number
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import request from '@/config/axios'
|
||||
import request from '@/axios'
|
||||
import type { UserType } from './types'
|
||||
|
||||
interface RoleParams {
|
||||
|
@ -6,29 +6,19 @@ interface RoleParams {
|
|||
}
|
||||
|
||||
export const loginApi = (data: UserType): Promise<IResponse<UserType>> => {
|
||||
return request.post({ url: '/user/login', data })
|
||||
return request.post({ url: '/mock/user/login', data })
|
||||
}
|
||||
|
||||
export const loginOutApi = (): Promise<IResponse> => {
|
||||
return request.get({ url: '/user/loginOut' })
|
||||
}
|
||||
|
||||
export const getUserListApi = ({ params }: AxiosConfig) => {
|
||||
return request.get<{
|
||||
code: string
|
||||
data: {
|
||||
list: UserType[]
|
||||
total: number
|
||||
}
|
||||
}>({ url: '/user/list', params })
|
||||
return request.get({ url: '/mock/user/loginOut' })
|
||||
}
|
||||
|
||||
export const getAdminRoleApi = (
|
||||
params: RoleParams
|
||||
): Promise<IResponse<AppCustomRouteRecordRaw[]>> => {
|
||||
return request.get({ url: '/role/list', params })
|
||||
return request.get({ url: '/mock/role/list', params })
|
||||
}
|
||||
|
||||
export const getTestRoleApi = (params: RoleParams): Promise<IResponse<string[]>> => {
|
||||
return request.get({ url: '/role/list', params })
|
||||
return request.get({ url: '/mock/role/list2', params })
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
export const getMenuListApi = () => {
|
||||
return request.get({ url: '/menu/list' })
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import request from '@/config/axios'
|
||||
import type { TableData } from './types'
|
||||
|
||||
export const getTableListApi = (params: any) => {
|
||||
return request.get({ url: '/example/list', params })
|
||||
}
|
||||
|
||||
export const getTreeTableListApi = (params: any) => {
|
||||
return request.get({ url: '/example/treeList', params })
|
||||
}
|
||||
|
||||
export const saveTableApi = (data: Partial<TableData>): Promise<IResponse> => {
|
||||
return request.post({ url: '/example/save', data })
|
||||
}
|
||||
|
||||
export const getTableDetApi = (id: string): Promise<IResponse<TableData>> => {
|
||||
return request.get({ url: '/example/detail', params: { id } })
|
||||
}
|
||||
|
||||
export const delTableListApi = (ids: string[] | number[]): Promise<IResponse> => {
|
||||
return request.post({ url: '/example/delete', data: { ids } })
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
export type TableData = {
|
||||
id: string
|
||||
author: string
|
||||
title: string
|
||||
content: string
|
||||
importance: number
|
||||
display_time: string
|
||||
pageviews: number
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import { AxiosResponse, InternalAxiosRequestConfig } from './types'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import qs from 'qs'
|
||||
import { SUCCESS_CODE, TRANSFORM_REQUEST_DATA } from '@/constants'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
import { objToFormData } from '@/utils'
|
||||
|
||||
const defaultRequestInterceptors = (config: InternalAxiosRequestConfig) => {
|
||||
if (
|
||||
config.method === 'post' &&
|
||||
config.headers['Content-Type'] === 'application/x-www-form-urlencoded'
|
||||
) {
|
||||
config.data = qs.stringify(config.data)
|
||||
} else if (
|
||||
TRANSFORM_REQUEST_DATA &&
|
||||
config.method === 'post' &&
|
||||
config.headers['Content-Type'] === 'multipart/form-data'
|
||||
) {
|
||||
config.data = objToFormData(config.data)
|
||||
}
|
||||
if (config.method === 'get' && config.params) {
|
||||
let url = config.url as string
|
||||
url += '?'
|
||||
const keys = Object.keys(config.params)
|
||||
for (const key of keys) {
|
||||
if (config.params[key] !== void 0 && config.params[key] !== null) {
|
||||
url += `${key}=${encodeURIComponent(config.params[key])}&`
|
||||
}
|
||||
}
|
||||
url = url.substring(0, url.length - 1)
|
||||
config.params = {}
|
||||
config.url = url
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
const defaultResponseInterceptors = (response: AxiosResponse) => {
|
||||
if (response?.config?.responseType === 'blob') {
|
||||
// 如果是文件流,直接过
|
||||
return response
|
||||
} else if (response.data.code === SUCCESS_CODE) {
|
||||
return response.data
|
||||
} else {
|
||||
ElMessage.error(response?.data?.message)
|
||||
if (response?.data?.code === 401) {
|
||||
const userStore = useUserStoreWithOut()
|
||||
userStore.logout()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { defaultResponseInterceptors, defaultRequestInterceptors }
|
|
@ -1,11 +1,10 @@
|
|||
import service from './service'
|
||||
|
||||
import config from './config'
|
||||
|
||||
const { defaultHeaders } = config
|
||||
import { CONTENT_TYPE } from '@/constants'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
|
||||
const request = (option: AxiosConfig) => {
|
||||
const { url, method, params, data, headersType, responseType } = option
|
||||
const { url, method, params, data, headers, responseType } = option
|
||||
const userStore = useUserStoreWithOut()
|
||||
return service.request({
|
||||
url: url,
|
||||
method,
|
||||
|
@ -13,7 +12,9 @@ const request = (option: AxiosConfig) => {
|
|||
data,
|
||||
responseType: responseType,
|
||||
headers: {
|
||||
'Content-Type': headersType || defaultHeaders
|
||||
'Content-Type': CONTENT_TYPE,
|
||||
[userStore.getTokenKey ?? 'Authorization']: userStore.getToken ?? '',
|
||||
...headers
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,17 +1,16 @@
|
|||
import axios from 'axios'
|
||||
import config, { defaultRequestInterceptors, defaultResponseInterceptors } from './config'
|
||||
import axios, { AxiosError } from 'axios'
|
||||
import { defaultRequestInterceptors, defaultResponseInterceptors } from './config'
|
||||
|
||||
import { AxiosInstance, InternalAxiosRequestConfig, RequestConfig, AxiosResponse } from './types'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { REQUEST_TIMEOUT } from '@/constants'
|
||||
|
||||
const { interceptors, baseUrl } = config
|
||||
export const PATH_URL = baseUrl[import.meta.env.VITE_API_BASE_PATH]
|
||||
|
||||
const { requestInterceptors, responseInterceptors } = interceptors
|
||||
export const PATH_URL = import.meta.env.VITE_API_BASE_PATH
|
||||
|
||||
const abortControllerMap: Map<string, AbortController> = new Map()
|
||||
|
||||
const axiosInstance: AxiosInstance = axios.create({
|
||||
...config,
|
||||
timeout: REQUEST_TIMEOUT,
|
||||
baseURL: PATH_URL
|
||||
})
|
||||
|
||||
|
@ -27,13 +26,18 @@ axiosInstance.interceptors.response.use(
|
|||
(res: AxiosResponse) => {
|
||||
const url = res.config.url || ''
|
||||
abortControllerMap.delete(url)
|
||||
return res.data
|
||||
// 这里不能做任何处理,否则后面的 interceptors 拿不到完整的上下文了
|
||||
return res
|
||||
},
|
||||
(err: any) => err
|
||||
(error: AxiosError) => {
|
||||
console.log('err: ' + error) // for debug
|
||||
ElMessage.error(error.message)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
axiosInstance.interceptors.request.use(requestInterceptors || defaultRequestInterceptors)
|
||||
axiosInstance.interceptors.response.use(responseInterceptors || defaultResponseInterceptors)
|
||||
axiosInstance.interceptors.request.use(defaultRequestInterceptors)
|
||||
axiosInstance.interceptors.response.use(defaultResponseInterceptors)
|
||||
|
||||
const service = {
|
||||
request: (config: RequestConfig) => {
|
|
@ -15,18 +15,6 @@ interface RequestInterceptors<T> {
|
|||
responseInterceptors?: (config: T) => T
|
||||
responseInterceptorsCatch?: (err: any) => any
|
||||
}
|
||||
interface AxiosConfig<T = AxiosResponse> {
|
||||
baseUrl: {
|
||||
base: string
|
||||
dev: string
|
||||
pro: string
|
||||
test: string
|
||||
}
|
||||
code: number
|
||||
defaultHeaders: AxiosHeaders
|
||||
timeout: number
|
||||
interceptors: RequestInterceptors<T>
|
||||
}
|
||||
|
||||
interface RequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
|
||||
interceptors?: RequestInterceptors<T>
|
||||
|
@ -36,7 +24,6 @@ export {
|
|||
AxiosResponse,
|
||||
RequestInterceptors,
|
||||
RequestConfig,
|
||||
AxiosConfig,
|
||||
AxiosInstance,
|
||||
InternalAxiosRequestConfig,
|
||||
AxiosRequestHeaders,
|
|
@ -0,0 +1,3 @@
|
|||
import BaseButton from './src/Button.vue'
|
||||
|
||||
export { BaseButton }
|
|
@ -0,0 +1,121 @@
|
|||
<script setup lang="ts">
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { ElButton, ComponentSize, ButtonType } from 'element-plus'
|
||||
import { PropType, Component, computed, unref } from 'vue'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const getTheme = computed(() => appStore.getTheme)
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('button')
|
||||
|
||||
const props = defineProps({
|
||||
size: {
|
||||
type: String as PropType<ComponentSize>,
|
||||
default: undefined
|
||||
},
|
||||
type: {
|
||||
type: String as PropType<ButtonType>,
|
||||
default: 'default'
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
plain: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
text: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
bg: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
link: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
round: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
circle: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loadingIcon: {
|
||||
type: [String, Object] as PropType<String | Component>,
|
||||
default: undefined
|
||||
},
|
||||
icon: {
|
||||
type: [String, Object] as PropType<String | Component>,
|
||||
default: undefined
|
||||
},
|
||||
autofocus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
nativeType: {
|
||||
type: String as PropType<'button' | 'submit' | 'reset'>,
|
||||
default: 'button'
|
||||
},
|
||||
autoInsertSpace: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
darker: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
tag: {
|
||||
type: [String, Object] as PropType<String | Component>,
|
||||
default: 'button'
|
||||
}
|
||||
})
|
||||
|
||||
const emits = defineEmits(['click'])
|
||||
|
||||
const color = computed(() => {
|
||||
const { type } = props
|
||||
if (type === 'primary') {
|
||||
return unref(getTheme).elColorPrimary
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
const style = computed(() => {
|
||||
const { type } = props
|
||||
if (type === 'primary') {
|
||||
return '--el-button-text-color: #fff; --el-button-hover-text-color: #fff'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElButton
|
||||
:class="`${prefixCls} color-#fff`"
|
||||
v-bind="{ ...props }"
|
||||
:color="color"
|
||||
:style="style"
|
||||
@click="() => emits('click')"
|
||||
>
|
||||
<slot></slot>
|
||||
<slot name="icon"></slot>
|
||||
<slot name="loading"></slot>
|
||||
</ElButton>
|
||||
</template>
|
|
@ -23,13 +23,12 @@ const toggleCollapse = () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="prefixCls">
|
||||
<div :class="prefixCls" @click="toggleCollapse">
|
||||
<Icon
|
||||
:size="18"
|
||||
:icon="collapse ? 'ant-design:menu-unfold-outlined' : 'ant-design:menu-fold-outlined'"
|
||||
:color="color"
|
||||
class="cursor-pointer"
|
||||
@click="toggleCollapse"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
import CountTo from './src/CountTo.vue'
|
||||
|
||||
export { CountTo }
|
|
@ -1,180 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { reactive, computed, watch, onMounted, unref, toRef, PropType } from 'vue'
|
||||
import { isNumber } from '@/utils/is'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('count-to')
|
||||
|
||||
const props = defineProps({
|
||||
startVal: propTypes.number.def(0),
|
||||
endVal: propTypes.number.def(2021),
|
||||
duration: propTypes.number.def(3000),
|
||||
autoplay: propTypes.bool.def(true),
|
||||
decimals: propTypes.number.validate((value: number) => value >= 0).def(0),
|
||||
decimal: propTypes.string.def('.'),
|
||||
separator: propTypes.string.def(','),
|
||||
prefix: propTypes.string.def(''),
|
||||
suffix: propTypes.string.def(''),
|
||||
useEasing: propTypes.bool.def(true),
|
||||
easingFn: {
|
||||
type: Function as PropType<(t: number, b: number, c: number, d: number) => number>,
|
||||
default(t: number, b: number, c: number, d: number) {
|
||||
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['mounted', 'callback'])
|
||||
|
||||
const formatNumber = (num: number | string) => {
|
||||
const { decimals, decimal, separator, suffix, prefix } = props
|
||||
num = Number(num).toFixed(decimals)
|
||||
num += ''
|
||||
const x = num.split('.')
|
||||
let x1 = x[0]
|
||||
const x2 = x.length > 1 ? decimal + x[1] : ''
|
||||
const rgx = /(\d+)(\d{3})/
|
||||
if (separator && !isNumber(separator)) {
|
||||
while (rgx.test(x1)) {
|
||||
x1 = x1.replace(rgx, '$1' + separator + '$2')
|
||||
}
|
||||
}
|
||||
return prefix + x1 + x2 + suffix
|
||||
}
|
||||
|
||||
const state = reactive<{
|
||||
localStartVal: number
|
||||
printVal: number | null
|
||||
displayValue: string
|
||||
paused: boolean
|
||||
localDuration: number | null
|
||||
startTime: number | null
|
||||
timestamp: number | null
|
||||
rAF: any
|
||||
remaining: number | null
|
||||
}>({
|
||||
localStartVal: props.startVal,
|
||||
displayValue: formatNumber(props.startVal),
|
||||
printVal: null,
|
||||
paused: false,
|
||||
localDuration: props.duration,
|
||||
startTime: null,
|
||||
timestamp: null,
|
||||
remaining: null,
|
||||
rAF: null
|
||||
})
|
||||
|
||||
const displayValue = toRef(state, 'displayValue')
|
||||
|
||||
onMounted(() => {
|
||||
if (props.autoplay) {
|
||||
start()
|
||||
}
|
||||
emit('mounted')
|
||||
})
|
||||
|
||||
const getCountDown = computed(() => {
|
||||
return props.startVal > props.endVal
|
||||
})
|
||||
|
||||
watch([() => props.startVal, () => props.endVal], () => {
|
||||
if (props.autoplay) {
|
||||
start()
|
||||
}
|
||||
})
|
||||
|
||||
const start = () => {
|
||||
const { startVal, duration } = props
|
||||
state.localStartVal = startVal
|
||||
state.startTime = null
|
||||
state.localDuration = duration
|
||||
state.paused = false
|
||||
state.rAF = requestAnimationFrame(count)
|
||||
}
|
||||
|
||||
const pauseResume = () => {
|
||||
if (state.paused) {
|
||||
resume()
|
||||
state.paused = false
|
||||
} else {
|
||||
pause()
|
||||
state.paused = true
|
||||
}
|
||||
}
|
||||
|
||||
const pause = () => {
|
||||
cancelAnimationFrame(state.rAF)
|
||||
}
|
||||
|
||||
const resume = () => {
|
||||
state.startTime = null
|
||||
state.localDuration = +(state.remaining as number)
|
||||
state.localStartVal = +(state.printVal as number)
|
||||
requestAnimationFrame(count)
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
state.startTime = null
|
||||
cancelAnimationFrame(state.rAF)
|
||||
state.displayValue = formatNumber(props.startVal)
|
||||
}
|
||||
|
||||
const count = (timestamp: number) => {
|
||||
const { useEasing, easingFn, endVal } = props
|
||||
if (!state.startTime) state.startTime = timestamp
|
||||
state.timestamp = timestamp
|
||||
const progress = timestamp - state.startTime
|
||||
state.remaining = (state.localDuration as number) - progress
|
||||
if (useEasing) {
|
||||
if (unref(getCountDown)) {
|
||||
state.printVal =
|
||||
state.localStartVal -
|
||||
easingFn(progress, 0, state.localStartVal - endVal, state.localDuration as number)
|
||||
} else {
|
||||
state.printVal = easingFn(
|
||||
progress,
|
||||
state.localStartVal,
|
||||
endVal - state.localStartVal,
|
||||
state.localDuration as number
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (unref(getCountDown)) {
|
||||
state.printVal =
|
||||
state.localStartVal -
|
||||
(state.localStartVal - endVal) * (progress / (state.localDuration as number))
|
||||
} else {
|
||||
state.printVal =
|
||||
state.localStartVal +
|
||||
(endVal - state.localStartVal) * (progress / (state.localDuration as number))
|
||||
}
|
||||
}
|
||||
if (unref(getCountDown)) {
|
||||
state.printVal = state.printVal < endVal ? endVal : state.printVal
|
||||
} else {
|
||||
state.printVal = state.printVal > endVal ? endVal : state.printVal
|
||||
}
|
||||
state.displayValue = formatNumber(state.printVal!)
|
||||
if (progress < (state.localDuration as number)) {
|
||||
state.rAF = requestAnimationFrame(count)
|
||||
} else {
|
||||
emit('callback')
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
pauseResume,
|
||||
reset,
|
||||
start,
|
||||
pause
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span :class="prefixCls">
|
||||
{{ displayValue }}
|
||||
</span>
|
||||
</template>
|
|
@ -1,3 +0,0 @@
|
|||
import Echart from './src/Echart.vue'
|
||||
|
||||
export { Echart }
|
|
@ -1,113 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import type { EChartsOption } from 'echarts'
|
||||
import echarts from '@/plugins/echarts'
|
||||
import { debounce } from 'lodash-es'
|
||||
import 'echarts-wordcloud'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { computed, PropType, ref, unref, watch, onMounted, onBeforeUnmount, onActivated } from 'vue'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { isString } from '@/utils/is'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
|
||||
const { getPrefixCls, variables } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('echart')
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const props = defineProps({
|
||||
options: {
|
||||
type: Object as PropType<EChartsOption>,
|
||||
required: true
|
||||
},
|
||||
width: propTypes.oneOfType([Number, String]).def(''),
|
||||
height: propTypes.oneOfType([Number, String]).def('500px')
|
||||
})
|
||||
|
||||
const isDark = computed(() => appStore.getIsDark)
|
||||
|
||||
const theme = computed(() => {
|
||||
const echartTheme: boolean | string = unref(isDark) ? true : 'auto'
|
||||
|
||||
return echartTheme
|
||||
})
|
||||
|
||||
const options = computed(() => {
|
||||
return Object.assign(props.options, {
|
||||
darkMode: unref(theme)
|
||||
})
|
||||
})
|
||||
|
||||
const elRef = ref<ElRef>()
|
||||
|
||||
let echartRef: Nullable<echarts.ECharts> = null
|
||||
|
||||
const contentEl = ref<Element>()
|
||||
|
||||
const styles = computed(() => {
|
||||
const width = isString(props.width) ? props.width : `${props.width}px`
|
||||
const height = isString(props.height) ? props.height : `${props.height}px`
|
||||
|
||||
return {
|
||||
width,
|
||||
height
|
||||
}
|
||||
})
|
||||
|
||||
const initChart = () => {
|
||||
if (unref(elRef) && props.options) {
|
||||
echartRef = echarts.init(unref(elRef) as HTMLElement)
|
||||
echartRef?.setOption(unref(options))
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => options.value,
|
||||
(options) => {
|
||||
if (echartRef) {
|
||||
echartRef?.setOption(options)
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
const resizeHandler = debounce(() => {
|
||||
if (echartRef) {
|
||||
echartRef.resize()
|
||||
}
|
||||
}, 100)
|
||||
|
||||
const contentResizeHandler = async (e: TransitionEvent) => {
|
||||
if (e.propertyName === 'width') {
|
||||
resizeHandler()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initChart()
|
||||
|
||||
window.addEventListener('resize', resizeHandler)
|
||||
|
||||
contentEl.value = document.getElementsByClassName(`${variables.namespace}-layout-content`)[0]
|
||||
unref(contentEl) &&
|
||||
(unref(contentEl) as Element).addEventListener('transitionend', contentResizeHandler)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', resizeHandler)
|
||||
unref(contentEl) &&
|
||||
(unref(contentEl) as Element).removeEventListener('transitionend', contentResizeHandler)
|
||||
})
|
||||
|
||||
onActivated(() => {
|
||||
if (echartRef) {
|
||||
echartRef.resize()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="elRef" :class="[$attrs.class, prefixCls]" :style="styles"></div>
|
||||
</template>
|
|
@ -15,7 +15,7 @@ const title = computed(() => appStore.getTitle)
|
|||
<template>
|
||||
<div
|
||||
:class="prefixCls"
|
||||
class="text-center text-[var(--el-text-color-placeholder)] bg-[var(--app-content-bg-color)] h-[var(--app-footer-height)] leading-[var(--app-footer-height)] dark:bg-[var(--el-bg-color)]"
|
||||
class="shrink-0 text-center text-[var(--el-text-color-placeholder)] bg-[var(--app-content-bg-color)] h-[var(--app-footer-height)] leading-[var(--app-footer-height)] dark:bg-[var(--el-bg-color)]"
|
||||
>
|
||||
Copyright ©2021-present {{ title }}
|
||||
</div>
|
||||
|
|
|
@ -93,9 +93,6 @@ export default defineComponent({
|
|||
// element form 实例
|
||||
const elFormRef = ref<ComponentRef<typeof ElForm>>()
|
||||
|
||||
// useForm传入的props
|
||||
const outsideProps = ref<FormProps>({})
|
||||
|
||||
const mergeProps = ref<FormProps>({})
|
||||
|
||||
const getProps = computed(() => {
|
||||
|
@ -124,8 +121,6 @@ export default defineComponent({
|
|||
|
||||
const setProps = (props: FormProps = {}) => {
|
||||
mergeProps.value = Object.assign(unref(mergeProps), props)
|
||||
// @ts-ignore
|
||||
outsideProps.value = props
|
||||
}
|
||||
|
||||
const delSchema = (field: string) => {
|
||||
|
@ -235,7 +230,7 @@ export default defineComponent({
|
|||
const { schema = [], isCol } = unref(getProps)
|
||||
|
||||
return schema
|
||||
.filter((v) => !v.remove)
|
||||
.filter((v) => !v.remove && !v.hidden)
|
||||
.map((item) => {
|
||||
// 如果是 Divider 组件,需要自己占用一行
|
||||
const isDivider = item.component === 'Divider'
|
||||
|
@ -334,7 +329,11 @@ export default defineComponent({
|
|||
ref={(el: any) => setComponentRefMap(el, item.field)}
|
||||
{...(autoSetPlaceholder && setTextPlaceholder(item))}
|
||||
{...setComponentProps(item)}
|
||||
style={item.componentProps?.style || {}}
|
||||
style={
|
||||
item.componentProps?.style || {
|
||||
width: '100%'
|
||||
}
|
||||
}
|
||||
>
|
||||
{{ ...slotsMap }}
|
||||
</Com>
|
||||
|
@ -406,4 +405,16 @@ export default defineComponent({
|
|||
margin-right: 0 !important;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
.@{elNamespace}-form--inline {
|
||||
:deep(.el-form-item__content) {
|
||||
& > :first-child {
|
||||
min-width: 229.5px;
|
||||
}
|
||||
}
|
||||
.@{elNamespace}-input-number {
|
||||
// 229.5px是兼容el-input-number的最小宽度,
|
||||
min-width: 229.5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -146,7 +146,7 @@ export const initModel = (schema: FormSchema[], formModel: Recordable) => {
|
|||
schema.map((v) => {
|
||||
if (v.remove) {
|
||||
delete model[v.field]
|
||||
} else if (v.component && v.component !== 'Divider') {
|
||||
} else if (v.component !== 'Divider') {
|
||||
// const hasField = Reflect.has(model, v.field)
|
||||
const hasField = get(model, v.field)
|
||||
// 如果先前已经有值存在,则不进行重新赋值,而是采用现有的值
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
import Highlight from './src/Highlight.vue'
|
||||
|
||||
export { Highlight }
|
|
@ -1,65 +0,0 @@
|
|||
<script lang="tsx">
|
||||
import { defineComponent, PropType, computed, h, unref } from 'vue'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Highlight',
|
||||
props: {
|
||||
tag: propTypes.string.def('span'),
|
||||
keys: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => []
|
||||
},
|
||||
color: propTypes.string.def('var(--el-color-primary)')
|
||||
},
|
||||
emits: ['click'],
|
||||
setup(props, { emit, slots }) {
|
||||
const keyNodes = computed(() => {
|
||||
return props.keys.map((key) => {
|
||||
return h(
|
||||
'span',
|
||||
{
|
||||
onClick: () => {
|
||||
emit('click', key)
|
||||
},
|
||||
style: {
|
||||
color: props.color,
|
||||
cursor: 'pointer'
|
||||
}
|
||||
},
|
||||
key
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
const parseText = (text: string) => {
|
||||
props.keys.forEach((key, index) => {
|
||||
const regexp = new RegExp(key, 'g')
|
||||
text = text.replace(regexp, `{{${index}}}`)
|
||||
})
|
||||
return text.split(/{{|}}/)
|
||||
}
|
||||
|
||||
const renderText = () => {
|
||||
if (!slots?.default) return null
|
||||
const node = slots?.default()[0].children
|
||||
|
||||
if (!node) {
|
||||
return slots?.default()[0]
|
||||
}
|
||||
|
||||
const textArray = parseText(node as string)
|
||||
const regexp = /^[0-9]*$/
|
||||
const nodes = textArray.map((t) => {
|
||||
if (regexp.test(t)) {
|
||||
return unref(keyNodes)[t] || t
|
||||
}
|
||||
return t
|
||||
})
|
||||
return h(props.tag, nodes)
|
||||
}
|
||||
|
||||
return () => renderText()
|
||||
}
|
||||
})
|
||||
</script>
|
|
@ -25,6 +25,11 @@ const symbolId = computed(() => {
|
|||
return unref(isLocal) ? `#icon-${props.icon.split('svg-icon:')[1]}` : props.icon
|
||||
})
|
||||
|
||||
// 是否使用在线图标
|
||||
const isUseOnline = computed(() => {
|
||||
return import.meta.env.VITE_USE_ONLINE_ICON === 'true'
|
||||
})
|
||||
|
||||
const getIconifyStyle = computed(() => {
|
||||
const { color, size } = props
|
||||
return {
|
||||
|
@ -40,7 +45,10 @@ const getIconifyStyle = computed(() => {
|
|||
<use :xlink:href="symbolId" />
|
||||
</svg>
|
||||
|
||||
<Icon v-else :icon="icon" :style="getIconifyStyle" />
|
||||
<template v-else>
|
||||
<Icon v-if="isUseOnline" :icon="icon" :style="getIconifyStyle" />
|
||||
<div v-else :class="`${icon} iconify`" :style="getIconifyStyle"></div>
|
||||
</template>
|
||||
</ElIcon>
|
||||
</template>
|
||||
|
||||
|
@ -49,11 +57,18 @@ const getIconifyStyle = computed(() => {
|
|||
|
||||
.@{prefix-cls},
|
||||
.iconify {
|
||||
&:hover {
|
||||
:deep(svg) {
|
||||
:deep(svg) {
|
||||
&:hover {
|
||||
// stylelint-disable-next-line
|
||||
color: v-bind(hoverColor) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.iconify {
|
||||
&:hover {
|
||||
// stylelint-disable-next-line
|
||||
color: v-bind(hoverColor) !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
import ImageViewer from './src/ImageViewer.vue'
|
||||
import { isClient } from '@/utils/is'
|
||||
import { createVNode, render, VNode } from 'vue'
|
||||
import { ImageViewerProps } from './src/types'
|
||||
|
||||
let instance: Nullable<VNode> = null
|
||||
|
||||
export function createImageViewer(options: ImageViewerProps) {
|
||||
if (!isClient) return
|
||||
const {
|
||||
urlList,
|
||||
initialIndex = 0,
|
||||
infinite = true,
|
||||
hideOnClickModal = false,
|
||||
teleported = false,
|
||||
zIndex = 2000,
|
||||
show = true
|
||||
} = options
|
||||
|
||||
const propsData: Partial<ImageViewerProps> = {}
|
||||
const container = document.createElement('div')
|
||||
propsData.urlList = urlList
|
||||
propsData.initialIndex = initialIndex
|
||||
propsData.infinite = infinite
|
||||
propsData.hideOnClickModal = hideOnClickModal
|
||||
propsData.teleported = teleported
|
||||
propsData.zIndex = zIndex
|
||||
propsData.show = show
|
||||
|
||||
document.body.appendChild(container)
|
||||
instance = createVNode(ImageViewer, propsData)
|
||||
render(instance, container)
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { ElImageViewer } from 'element-plus'
|
||||
import { computed, ref, PropType } from 'vue'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
|
||||
const props = defineProps({
|
||||
urlList: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: (): string[] => []
|
||||
},
|
||||
zIndex: propTypes.number.def(200),
|
||||
initialIndex: propTypes.number.def(0),
|
||||
infinite: propTypes.bool.def(true),
|
||||
hideOnClickModal: propTypes.bool.def(false),
|
||||
teleported: propTypes.bool.def(false),
|
||||
show: propTypes.bool.def(false)
|
||||
})
|
||||
|
||||
const getBindValue = computed(() => {
|
||||
const propsData: Recordable = { ...props }
|
||||
delete propsData.show
|
||||
return propsData
|
||||
})
|
||||
|
||||
const show = ref(props.show)
|
||||
|
||||
const close = () => {
|
||||
show.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElImageViewer v-if="show" v-bind="getBindValue" @close="close" />
|
||||
</template>
|
|
@ -1,9 +0,0 @@
|
|||
export interface ImageViewerProps {
|
||||
urlList?: string[]
|
||||
zIndex?: number
|
||||
initialIndex?: number
|
||||
infinite?: boolean
|
||||
hideOnClickModal?: boolean
|
||||
teleported?: boolean
|
||||
show?: boolean
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
import Infotip from './src/Infotip.vue'
|
||||
|
||||
export type { InfoTipSchema } from './src/types'
|
||||
|
||||
export { Infotip }
|
|
@ -1,53 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { PropType } from 'vue'
|
||||
import { Highlight } from '@/components/Highlight'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { InfoTipSchema } from './types'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('infotip')
|
||||
|
||||
defineProps({
|
||||
title: propTypes.string.def(''),
|
||||
schema: {
|
||||
type: Array as PropType<Array<string | InfoTipSchema>>,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
showIndex: propTypes.bool.def(true),
|
||||
highlightColor: propTypes.string.def('var(--el-color-primary)')
|
||||
})
|
||||
|
||||
const emit = defineEmits(['click'])
|
||||
|
||||
const keyClick = (key: string) => {
|
||||
emit('click', key)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
prefixCls,
|
||||
'p-20px mb-20px border-1px border-solid border-[var(--el-color-primary)] bg-[var(--el-color-primary-light-9)]'
|
||||
]"
|
||||
>
|
||||
<div v-if="title" :class="[`${prefixCls}__header`, 'flex items-center']">
|
||||
<Icon icon="bi:exclamation-circle-fill" :size="22" color="var(--el-color-primary)" />
|
||||
<span :class="[`${prefixCls}__title`, 'pl-5px text-16px font-bold']">{{ title }}</span>
|
||||
</div>
|
||||
<div :class="`${prefixCls}__content`">
|
||||
<p v-for="(item, $index) in schema" :key="$index" class="text-14px mt-15px">
|
||||
<Highlight
|
||||
:keys="typeof item === 'string' ? [] : item.keys"
|
||||
:color="highlightColor"
|
||||
@click="keyClick"
|
||||
>
|
||||
{{ showIndex ? `${$index + 1}、` : '' }}{{ typeof item === 'string' ? item : item.label }}
|
||||
</Highlight>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,4 +0,0 @@
|
|||
export interface InfoTipSchema {
|
||||
label: string
|
||||
keys?: string[]
|
||||
}
|
|
@ -89,11 +89,16 @@ export default defineComponent({
|
|||
backgroundColor="var(--left-menu-bg-color)"
|
||||
textColor="var(--left-menu-text-color)"
|
||||
activeTextColor="var(--left-menu-text-active-color)"
|
||||
popperClass={
|
||||
unref(menuMode) === 'vertical'
|
||||
? `${prefixCls}-popper--vertical`
|
||||
: `${prefixCls}-popper--horizontal`
|
||||
}
|
||||
onSelect={menuSelect}
|
||||
>
|
||||
{{
|
||||
default: () => {
|
||||
const { renderMenuItem } = useRenderMenuItem(unref(menuMode))
|
||||
const { renderMenuItem } = useRenderMenuItem()
|
||||
return renderMenuItem(unref(routers))
|
||||
}
|
||||
}}
|
||||
|
@ -123,30 +128,10 @@ export default defineComponent({
|
|||
<style lang="less" scoped>
|
||||
@prefix-cls: ~'@{namespace}-menu';
|
||||
|
||||
// .is-active--after {
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// right: 0;
|
||||
// width: 4px;
|
||||
// height: 100%;
|
||||
// background-color: var(--el-color-primary);
|
||||
// content: '';
|
||||
// }
|
||||
|
||||
.@{prefix-cls} {
|
||||
position: relative;
|
||||
transition: width var(--transition-time-02);
|
||||
|
||||
// &:after {
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// right: 0;
|
||||
// height: 100%;
|
||||
// width: 1px;
|
||||
// background-color: var(--el-border-color);
|
||||
// content: '';
|
||||
// }
|
||||
|
||||
:deep(.@{elNamespace}-menu) {
|
||||
width: 100% !important;
|
||||
border-right: none;
|
||||
|
@ -168,7 +153,6 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
// 设置选中时的高亮背景和高亮颜色
|
||||
.@{elNamespace}-sub-menu.is-active,
|
||||
.@{elNamespace}-menu-item.is-active {
|
||||
color: var(--left-menu-text-active-color) !important;
|
||||
background-color: var(--left-menu-bg-active-color) !important;
|
||||
|
@ -180,10 +164,6 @@ export default defineComponent({
|
|||
|
||||
.@{elNamespace}-menu-item.is-active {
|
||||
position: relative;
|
||||
|
||||
// &:after {
|
||||
// .is-active--after;
|
||||
// }
|
||||
}
|
||||
|
||||
// 设置子菜单的背景颜色
|
||||
|
@ -203,16 +183,11 @@ export default defineComponent({
|
|||
& > .is-active > .@{elNamespace}-sub-menu__title {
|
||||
position: relative;
|
||||
background-color: var(--left-menu-collapse-bg-active-color) !important;
|
||||
|
||||
// &:after {
|
||||
// .is-active--after;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// 折叠动画的时候,就需要把文字给隐藏掉
|
||||
:deep(.horizontal-collapse-transition) {
|
||||
// transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out !important;
|
||||
.@{prefix-cls}__title {
|
||||
display: none;
|
||||
}
|
||||
|
@ -235,7 +210,7 @@ export default defineComponent({
|
|||
.@{elNamespace}-menu-item.is-active {
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
&::after {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
@ -254,16 +229,6 @@ export default defineComponent({
|
|||
<style lang="less">
|
||||
@prefix-cls: ~'@{namespace}-menu-popper';
|
||||
|
||||
// .is-active--after {
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// right: 0;
|
||||
// width: 4px;
|
||||
// height: 100%;
|
||||
// background-color: var(--el-color-primary);
|
||||
// content: '';
|
||||
// }
|
||||
|
||||
.@{prefix-cls}--vertical,
|
||||
.@{prefix-cls}--horizontal {
|
||||
// 设置选中时子标题的颜色
|
||||
|
@ -290,10 +255,6 @@ export default defineComponent({
|
|||
&:hover {
|
||||
background-color: var(--left-menu-bg-active-color) !important;
|
||||
}
|
||||
|
||||
// &:after {
|
||||
// .is-active--after;
|
||||
// }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,59 +1,50 @@
|
|||
import { ElSubMenu, ElMenuItem } from 'element-plus'
|
||||
import type { RouteMeta } from 'vue-router'
|
||||
import { hasOneShowingChild } from '../helper'
|
||||
import { isUrl } from '@/utils/is'
|
||||
import { useRenderMenuTitle } from './useRenderMenuTitle'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { pathResolve } from '@/utils/routerHelper'
|
||||
|
||||
export const useRenderMenuItem = (
|
||||
const { renderMenuTitle } = useRenderMenuTitle()
|
||||
|
||||
export const useRenderMenuItem = () =>
|
||||
// allRouters: AppRouteRecordRaw[] = [],
|
||||
menuMode: 'vertical' | 'horizontal'
|
||||
) => {
|
||||
const renderMenuItem = (routers: AppRouteRecordRaw[], parentPath = '/') => {
|
||||
return routers.map((v) => {
|
||||
const meta = (v.meta ?? {}) as RouteMeta
|
||||
if (!meta.hidden) {
|
||||
const { oneShowingChild, onlyOneChild } = hasOneShowingChild(v.children, v)
|
||||
const fullPath = isUrl(v.path) ? v.path : pathResolve(parentPath, v.path) // getAllParentPath<AppRouteRecordRaw>(allRouters, v.path).join('/')
|
||||
{
|
||||
const renderMenuItem = (routers: AppRouteRecordRaw[], parentPath = '/') => {
|
||||
return routers
|
||||
.filter((v) => !v.meta?.hidden)
|
||||
.map((v) => {
|
||||
const meta = v.meta ?? {}
|
||||
const { oneShowingChild, onlyOneChild } = hasOneShowingChild(v.children, v)
|
||||
const fullPath = isUrl(v.path) ? v.path : pathResolve(parentPath, v.path) // getAllParentPath<AppRouteRecordRaw>(allRouters, v.path).join('/')
|
||||
|
||||
const { renderMenuTitle } = useRenderMenuTitle()
|
||||
if (
|
||||
oneShowingChild &&
|
||||
(!onlyOneChild?.children || onlyOneChild?.noShowingChildren) &&
|
||||
!meta?.alwaysShow
|
||||
) {
|
||||
return (
|
||||
<ElMenuItem
|
||||
index={onlyOneChild ? pathResolve(fullPath, onlyOneChild.path) : fullPath}
|
||||
>
|
||||
{{
|
||||
default: () => renderMenuTitle(onlyOneChild ? onlyOneChild?.meta : meta)
|
||||
}}
|
||||
</ElMenuItem>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<ElSubMenu index={fullPath}>
|
||||
{{
|
||||
title: () => renderMenuTitle(meta),
|
||||
default: () => renderMenuItem(v.children!, fullPath)
|
||||
}}
|
||||
</ElSubMenu>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
oneShowingChild &&
|
||||
(!onlyOneChild?.children || onlyOneChild?.noShowingChildren) &&
|
||||
!meta?.alwaysShow
|
||||
) {
|
||||
return (
|
||||
<ElMenuItem index={onlyOneChild ? pathResolve(fullPath, onlyOneChild.path) : fullPath}>
|
||||
{{
|
||||
default: () => renderMenuTitle(onlyOneChild ? onlyOneChild?.meta : meta)
|
||||
}}
|
||||
</ElMenuItem>
|
||||
)
|
||||
} else {
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const preFixCls = getPrefixCls('menu-popper')
|
||||
return (
|
||||
<ElSubMenu
|
||||
index={fullPath}
|
||||
popperClass={
|
||||
menuMode === 'vertical' ? `${preFixCls}--vertical` : `${preFixCls}--horizontal`
|
||||
}
|
||||
>
|
||||
{{
|
||||
title: () => renderMenuTitle(meta),
|
||||
default: () => renderMenuItem(v.children!, fullPath)
|
||||
}}
|
||||
</ElSubMenu>
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
return {
|
||||
renderMenuItem
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
renderMenuItem
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,10 +10,14 @@ export const useRenderMenuTitle = () => {
|
|||
return icon ? (
|
||||
<>
|
||||
<Icon icon={meta.icon}></Icon>
|
||||
<span class="v-menu__title">{t(title as string)}</span>
|
||||
<span class="v-menu__title overflow-hidden overflow-ellipsis whitespace-nowrap">
|
||||
{t(title as string)}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span class="v-menu__title">{t(title as string)}</span>
|
||||
<span class="v-menu__title overflow-hidden overflow-ellipsis whitespace-nowrap">
|
||||
{t(title as string)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import type { RouteMeta } from 'vue-router'
|
||||
import { ref, unref } from 'vue'
|
||||
import { findPath } from '@/utils/tree'
|
||||
|
||||
|
@ -21,7 +20,7 @@ export const hasOneShowingChild = (
|
|||
const onlyOneChild = ref<OnlyOneChildType>()
|
||||
|
||||
const showingChildren = children.filter((v) => {
|
||||
const meta = (v.meta ?? {}) as RouteMeta
|
||||
const meta = v.meta ?? {}
|
||||
if (meta.hidden) {
|
||||
return false
|
||||
} else {
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import Qrcode from './src/Qrcode.vue'
|
||||
|
||||
export type { QrcodeLogo } from './src/types'
|
||||
|
||||
export { Qrcode }
|
|
@ -1,252 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { PropType, nextTick, ref, watch, computed, unref } from 'vue'
|
||||
import QRCode from 'qrcode'
|
||||
import { QRCodeRenderersOptions } from 'qrcode'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { isString } from '@/utils/is'
|
||||
import { QrcodeLogo } from '@/components/Qrcode'
|
||||
|
||||
const props = defineProps({
|
||||
// img 或者 canvas,img不支持logo嵌套
|
||||
tag: propTypes.string.validate((v: string) => ['canvas', 'img'].includes(v)).def('canvas'),
|
||||
// 二维码内容
|
||||
text: {
|
||||
type: [String, Array] as PropType<string | Recordable[]>,
|
||||
default: null
|
||||
},
|
||||
// qrcode.js配置项
|
||||
options: {
|
||||
type: Object as PropType<QRCodeRenderersOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
// 宽度
|
||||
width: propTypes.number.def(200),
|
||||
// logo
|
||||
logo: {
|
||||
type: [String, Object] as PropType<Partial<QrcodeLogo> | string>,
|
||||
default: ''
|
||||
},
|
||||
// 是否过期
|
||||
disabled: propTypes.bool.def(false),
|
||||
// 过期提示内容
|
||||
disabledText: propTypes.string.def('')
|
||||
})
|
||||
|
||||
const emit = defineEmits(['done', 'click', 'disabled-click'])
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('qrcode')
|
||||
|
||||
const { toCanvas, toDataURL } = QRCode
|
||||
|
||||
const loading = ref(true)
|
||||
|
||||
const wrapRef = ref<Nullable<HTMLCanvasElement | HTMLImageElement>>(null)
|
||||
|
||||
const renderText = computed(() => String(props.text))
|
||||
|
||||
const wrapStyle = computed(() => {
|
||||
return {
|
||||
width: props.width + 'px',
|
||||
height: props.width + 'px'
|
||||
}
|
||||
})
|
||||
|
||||
const initQrcode = async () => {
|
||||
await nextTick()
|
||||
const options = cloneDeep(props.options || {})
|
||||
if (props.tag === 'canvas') {
|
||||
// 容错率,默认对内容少的二维码采用高容错率,内容多的二维码采用低容错率
|
||||
options.errorCorrectionLevel =
|
||||
options.errorCorrectionLevel || getErrorCorrectionLevel(unref(renderText))
|
||||
const _width: number = await getOriginWidth(unref(renderText), options)
|
||||
options.scale = props.width === 0 ? undefined : (props.width / _width) * 4
|
||||
const canvasRef = (await toCanvas(
|
||||
unref(wrapRef) as HTMLCanvasElement,
|
||||
unref(renderText),
|
||||
options
|
||||
)) as unknown as HTMLCanvasElement
|
||||
if (props.logo) {
|
||||
const url = await createLogoCode(canvasRef)
|
||||
emit('done', url)
|
||||
loading.value = false
|
||||
} else {
|
||||
emit('done', canvasRef.toDataURL())
|
||||
loading.value = false
|
||||
}
|
||||
} else {
|
||||
const url = await toDataURL(renderText.value, {
|
||||
errorCorrectionLevel: 'H',
|
||||
width: props.width,
|
||||
...options
|
||||
})
|
||||
;(unref(wrapRef) as HTMLImageElement).src = url
|
||||
emit('done', url)
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => renderText.value,
|
||||
(val) => {
|
||||
if (!val) return
|
||||
initQrcode()
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
const createLogoCode = (canvasRef: HTMLCanvasElement) => {
|
||||
const canvasWidth = canvasRef.width
|
||||
const logoOptions: QrcodeLogo = Object.assign(
|
||||
{
|
||||
logoSize: 0.15,
|
||||
bgColor: '#ffffff',
|
||||
borderSize: 0.05,
|
||||
crossOrigin: 'anonymous',
|
||||
borderRadius: 8,
|
||||
logoRadius: 0
|
||||
},
|
||||
isString(props.logo) ? {} : props.logo
|
||||
)
|
||||
const {
|
||||
logoSize = 0.15,
|
||||
bgColor = '#ffffff',
|
||||
borderSize = 0.05,
|
||||
crossOrigin = 'anonymous',
|
||||
borderRadius = 8,
|
||||
logoRadius = 0
|
||||
} = logoOptions
|
||||
const logoSrc = isString(props.logo) ? props.logo : props.logo.src
|
||||
const logoWidth = canvasWidth * logoSize
|
||||
const logoXY = (canvasWidth * (1 - logoSize)) / 2
|
||||
const logoBgWidth = canvasWidth * (logoSize + borderSize)
|
||||
const logoBgXY = (canvasWidth * (1 - logoSize - borderSize)) / 2
|
||||
|
||||
const ctx = canvasRef.getContext('2d')
|
||||
if (!ctx) return
|
||||
|
||||
// logo 底色
|
||||
canvasRoundRect(ctx)(logoBgXY, logoBgXY, logoBgWidth, logoBgWidth, borderRadius)
|
||||
ctx.fillStyle = bgColor
|
||||
ctx.fill()
|
||||
|
||||
// logo
|
||||
const image = new Image()
|
||||
if (crossOrigin || logoRadius) {
|
||||
image.setAttribute('crossOrigin', crossOrigin)
|
||||
}
|
||||
;(image as any).src = logoSrc
|
||||
|
||||
// 使用image绘制可以避免某些跨域情况
|
||||
const drawLogoWithImage = (image: HTMLImageElement) => {
|
||||
ctx.drawImage(image, logoXY, logoXY, logoWidth, logoWidth)
|
||||
}
|
||||
|
||||
// 使用canvas绘制以获得更多的功能
|
||||
const drawLogoWithCanvas = (image: HTMLImageElement) => {
|
||||
const canvasImage = document.createElement('canvas')
|
||||
canvasImage.width = logoXY + logoWidth
|
||||
canvasImage.height = logoXY + logoWidth
|
||||
const imageCanvas = canvasImage.getContext('2d')
|
||||
if (!imageCanvas || !ctx) return
|
||||
imageCanvas.drawImage(image, logoXY, logoXY, logoWidth, logoWidth)
|
||||
|
||||
canvasRoundRect(ctx)(logoXY, logoXY, logoWidth, logoWidth, logoRadius)
|
||||
if (!ctx) return
|
||||
const fillStyle = ctx.createPattern(canvasImage, 'no-repeat')
|
||||
if (fillStyle) {
|
||||
ctx.fillStyle = fillStyle
|
||||
ctx.fill()
|
||||
}
|
||||
}
|
||||
|
||||
// 将 logo绘制到 canvas上
|
||||
return new Promise((resolve: any) => {
|
||||
image.onload = () => {
|
||||
logoRadius ? drawLogoWithCanvas(image) : drawLogoWithImage(image)
|
||||
resolve(canvasRef.toDataURL())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 得到原QrCode的大小,以便缩放得到正确的QrCode大小
|
||||
const getOriginWidth = async (content: string, options: QRCodeRenderersOptions) => {
|
||||
const _canvas = document.createElement('canvas')
|
||||
await toCanvas(_canvas, content, options)
|
||||
return _canvas.width
|
||||
}
|
||||
|
||||
// 对于内容少的QrCode,增大容错率
|
||||
const getErrorCorrectionLevel = (content: string) => {
|
||||
if (content.length > 36) {
|
||||
return 'M'
|
||||
} else if (content.length > 16) {
|
||||
return 'Q'
|
||||
} else {
|
||||
return 'H'
|
||||
}
|
||||
}
|
||||
|
||||
// copy来的方法,用于绘制圆角
|
||||
const canvasRoundRect = (ctx: CanvasRenderingContext2D) => {
|
||||
return (x: number, y: number, w: number, h: number, r: number) => {
|
||||
const minSize = Math.min(w, h)
|
||||
if (r > minSize / 2) {
|
||||
r = minSize / 2
|
||||
}
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x + r, y)
|
||||
ctx.arcTo(x + w, y, x + w, y + h, r)
|
||||
ctx.arcTo(x + w, y + h, x, y + h, r)
|
||||
ctx.arcTo(x, y + h, x, y, r)
|
||||
ctx.arcTo(x, y, x + w, y, r)
|
||||
ctx.closePath()
|
||||
return ctx
|
||||
}
|
||||
}
|
||||
|
||||
const clickCode = () => {
|
||||
emit('click')
|
||||
}
|
||||
|
||||
const disabledClick = () => {
|
||||
emit('disabled-click')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-loading="loading" :class="[prefixCls, 'relative inline-block']" :style="wrapStyle">
|
||||
<component :is="tag" ref="wrapRef" @click="clickCode" />
|
||||
<div
|
||||
v-if="disabled"
|
||||
:class="`${prefixCls}--disabled`"
|
||||
class="absolute top-0 left-0 flex w-full h-full items-center justify-center"
|
||||
@click="disabledClick"
|
||||
>
|
||||
<div class="absolute top-[50%] left-[50%] font-bold">
|
||||
<Icon icon="ep:refresh-right" :size="30" color="var(--el-color-primary)" />
|
||||
<div>{{ disabledText }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@prefix-cls: ~'@{namespace}-qrcode';
|
||||
|
||||
.@{prefix-cls} {
|
||||
&--disabled {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
|
||||
& > div {
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,9 +0,0 @@
|
|||
export interface QrcodeLogo {
|
||||
src?: string
|
||||
logoSize?: number
|
||||
bgColor?: string
|
||||
borderSize?: number
|
||||
crossOrigin?: string
|
||||
borderRadius?: number
|
||||
logoRadius?: number
|
||||
}
|
|
@ -88,6 +88,9 @@ const newSchema = computed(() => {
|
|||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
label: () => {
|
||||
return <span> </span>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -117,11 +120,14 @@ const setProps = (props: SearchProps = {}) => {
|
|||
outsideProps.value = props
|
||||
}
|
||||
|
||||
const schemaRef = ref<FormSchema[]>([])
|
||||
|
||||
// 监听表单结构化数组,重新生成formModel
|
||||
watch(
|
||||
() => unref(newSchema),
|
||||
async (schema = []) => {
|
||||
formModel.value = initModel(schema, unref(formModel))
|
||||
schemaRef.value = schema
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
|
@ -241,7 +247,7 @@ const onFormValidate = (prop: FormItemProp, isValid: boolean, message: string) =
|
|||
hide-required-asterisk
|
||||
:inline="getProps.inline"
|
||||
:is-col="getProps.isCol"
|
||||
:schema="newSchema"
|
||||
:schema="schemaRef"
|
||||
@register="formRegister"
|
||||
@validate="onFormValidate"
|
||||
/>
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
import { ElDrawer, ElDivider, ElButton, ElMessage } from 'element-plus'
|
||||
import { ref, unref, computed, watch } from 'vue'
|
||||
import { ElDrawer, ElDivider, ElMessage } from 'element-plus'
|
||||
import { ref, unref } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ThemeSwitch } from '@/components/ThemeSwitch'
|
||||
import { colorIsDark, lighten, hexToRGB } from '@/utils/color'
|
||||
import { useCssVar } from '@vueuse/core'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { trim, setCssVar } from '@/utils'
|
||||
import { trim, setCssVar, getCssVar } from '@/utils'
|
||||
import ColorRadioPicker from './components/ColorRadioPicker.vue'
|
||||
import InterfaceDisplay from './components/InterfaceDisplay.vue'
|
||||
import LayoutRadioPicker from './components/LayoutRadioPicker.vue'
|
||||
|
@ -14,7 +13,7 @@ import { useStorage } from '@/hooks/web/useStorage'
|
|||
import { useClipboard } from '@vueuse/core'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
|
||||
const { removeStorage } = useStorage()
|
||||
const { clear: storageClear } = useStorage('localStorage')
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
|
@ -24,8 +23,6 @@ const appStore = useAppStore()
|
|||
|
||||
const { t } = useI18n()
|
||||
|
||||
const layout = computed(() => appStore.getLayout)
|
||||
|
||||
const drawer = ref(false)
|
||||
|
||||
// 主题色相关
|
||||
|
@ -42,70 +39,28 @@ const setSystemTheme = (color: string) => {
|
|||
const headerTheme = ref(appStore.getTheme.topHeaderBgColor || '')
|
||||
|
||||
const setHeaderTheme = (color: string) => {
|
||||
const isDarkColor = colorIsDark(color)
|
||||
const textColor = isDarkColor ? '#fff' : 'inherit'
|
||||
const textHoverColor = isDarkColor ? lighten(color!, 6) : '#f6f6f6'
|
||||
const topToolBorderColor = isDarkColor ? color : '#eee'
|
||||
setCssVar('--top-header-bg-color', color)
|
||||
setCssVar('--top-header-text-color', textColor)
|
||||
setCssVar('--top-header-hover-color', textHoverColor)
|
||||
appStore.setTheme({
|
||||
topHeaderBgColor: color,
|
||||
topHeaderTextColor: textColor,
|
||||
topHeaderHoverColor: textHoverColor,
|
||||
topToolBorderColor
|
||||
})
|
||||
if (unref(layout) === 'top') {
|
||||
setMenuTheme(color)
|
||||
}
|
||||
appStore.setHeaderTheme(color)
|
||||
}
|
||||
|
||||
// 菜单主题相关
|
||||
const menuTheme = ref(appStore.getTheme.leftMenuBgColor || '')
|
||||
|
||||
const setMenuTheme = (color: string) => {
|
||||
const primaryColor = useCssVar('--el-color-primary', document.documentElement)
|
||||
const isDarkColor = colorIsDark(color)
|
||||
const theme: Recordable = {
|
||||
// 左侧菜单边框颜色
|
||||
leftMenuBorderColor: isDarkColor ? 'inherit' : '#eee',
|
||||
// 左侧菜单背景颜色
|
||||
leftMenuBgColor: color,
|
||||
// 左侧菜单浅色背景颜色
|
||||
leftMenuBgLightColor: isDarkColor ? lighten(color!, 6) : color,
|
||||
// 左侧菜单选中背景颜色
|
||||
leftMenuBgActiveColor: isDarkColor
|
||||
? 'var(--el-color-primary)'
|
||||
: hexToRGB(unref(primaryColor), 0.1),
|
||||
// 左侧菜单收起选中背景颜色
|
||||
leftMenuCollapseBgActiveColor: isDarkColor
|
||||
? 'var(--el-color-primary)'
|
||||
: hexToRGB(unref(primaryColor), 0.1),
|
||||
// 左侧菜单字体颜色
|
||||
leftMenuTextColor: isDarkColor ? '#bfcbd9' : '#333',
|
||||
// 左侧菜单选中字体颜色
|
||||
leftMenuTextActiveColor: isDarkColor ? '#fff' : 'var(--el-color-primary)',
|
||||
// logo字体颜色
|
||||
logoTitleTextColor: isDarkColor ? '#fff' : 'inherit',
|
||||
// logo边框颜色
|
||||
logoBorderColor: isDarkColor ? color : '#eee'
|
||||
}
|
||||
appStore.setTheme(theme)
|
||||
appStore.setCssVarTheme()
|
||||
appStore.setMenuTheme(color)
|
||||
}
|
||||
|
||||
// 监听layout变化,重置一些主题色
|
||||
watch(
|
||||
() => layout.value,
|
||||
(n) => {
|
||||
if (n === 'top' && !appStore.getIsDark) {
|
||||
headerTheme.value = '#fff'
|
||||
setHeaderTheme('#fff')
|
||||
} else {
|
||||
setMenuTheme(unref(menuTheme))
|
||||
}
|
||||
}
|
||||
)
|
||||
// watch(
|
||||
// () => layout.value,
|
||||
// (n) => {
|
||||
// if (n === 'top' && !appStore.getIsDark) {
|
||||
// headerTheme.value = '#fff'
|
||||
// setHeaderTheme('#fff')
|
||||
// } else {
|
||||
// setMenuTheme(unref(menuTheme))
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
|
||||
// 拷贝
|
||||
const copyConfig = async () => {
|
||||
|
@ -174,7 +129,8 @@ const copyConfig = async () => {
|
|||
// 头部边框颜色
|
||||
topToolBorderColor: '${appStore.getTheme.topToolBorderColor}'
|
||||
}
|
||||
`
|
||||
`,
|
||||
legacy: true
|
||||
})
|
||||
if (!isSupported) {
|
||||
ElMessage.error(t('setting.copyFailed'))
|
||||
|
@ -188,11 +144,15 @@ const copyConfig = async () => {
|
|||
|
||||
// 清空缓存
|
||||
const clear = () => {
|
||||
removeStorage('layout')
|
||||
removeStorage('theme')
|
||||
removeStorage('isDark')
|
||||
storageClear()
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
const themeChange = () => {
|
||||
const color = getCssVar('--el-bg-color')
|
||||
setMenuTheme(color)
|
||||
setHeaderTheme(color)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -212,7 +172,7 @@ const clear = () => {
|
|||
<div class="text-center">
|
||||
<!-- 主题 -->
|
||||
<ElDivider>{{ t('setting.theme') }}</ElDivider>
|
||||
<ThemeSwitch />
|
||||
<ThemeSwitch @change="themeChange" />
|
||||
|
||||
<!-- 布局 -->
|
||||
<ElDivider>{{ t('setting.layout') }}</ElDivider>
|
||||
|
@ -253,23 +213,21 @@ const clear = () => {
|
|||
/>
|
||||
|
||||
<!-- 菜单主题 -->
|
||||
<template v-if="layout !== 'top'">
|
||||
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
|
||||
<ColorRadioPicker
|
||||
v-model="menuTheme"
|
||||
:schema="[
|
||||
'#fff',
|
||||
'#001529',
|
||||
'#212121',
|
||||
'#273352',
|
||||
'#191b24',
|
||||
'#383f45',
|
||||
'#001628',
|
||||
'#344058'
|
||||
]"
|
||||
@change="setMenuTheme"
|
||||
/>
|
||||
</template>
|
||||
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
|
||||
<ColorRadioPicker
|
||||
v-model="menuTheme"
|
||||
:schema="[
|
||||
'#fff',
|
||||
'#001529',
|
||||
'#212121',
|
||||
'#273352',
|
||||
'#191b24',
|
||||
'#383f45',
|
||||
'#001628',
|
||||
'#344058'
|
||||
]"
|
||||
@change="setMenuTheme"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 界面显示 -->
|
||||
|
@ -278,12 +236,14 @@ const clear = () => {
|
|||
|
||||
<ElDivider />
|
||||
<div>
|
||||
<ElButton type="primary" class="w-full" @click="copyConfig">{{ t('setting.copy') }}</ElButton>
|
||||
<BaseButton type="primary" class="w-full" @click="copyConfig">{{
|
||||
t('setting.copy')
|
||||
}}</BaseButton>
|
||||
</div>
|
||||
<div class="mt-5px">
|
||||
<ElButton type="danger" class="w-full" @click="clear">
|
||||
<BaseButton type="danger" class="w-full" @click="clear">
|
||||
{{ t('setting.clearAndReset') }}
|
||||
</ElButton>
|
||||
</BaseButton>
|
||||
</div>
|
||||
</ElDrawer>
|
||||
</template>
|
||||
|
|
|
@ -108,13 +108,21 @@ const greyModeChange = (show: boolean) => {
|
|||
}
|
||||
|
||||
// 动态路由
|
||||
const dynamicRouter = ref(appStore.getDynamicRouter)
|
||||
const dynamicRouter = ref(!!appStore.getDynamicRouter)
|
||||
|
||||
const dynamicRouterChange = (show: boolean) => {
|
||||
ElMessage.info(t('setting.reExperienced'))
|
||||
appStore.setDynamicRouter(show)
|
||||
}
|
||||
|
||||
// 服务端动态路由
|
||||
const serverDynamicRouter = ref(appStore.getServerDynamicRouter)
|
||||
|
||||
const serverDynamicRouterChange = (show: boolean) => {
|
||||
ElMessage.info(t('setting.reExperienced'))
|
||||
appStore.setServerDynamicRouter(show)
|
||||
}
|
||||
|
||||
// 固定菜单
|
||||
const fixedMenu = ref(appStore.getFixedMenu)
|
||||
|
||||
|
@ -206,6 +214,11 @@ watch(
|
|||
<ElSwitch v-model="dynamicRouter" @change="dynamicRouterChange" />
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-14px">{{ t('setting.serverDynamicRouter') }}</span>
|
||||
<ElSwitch v-model="serverDynamicRouter" @change="serverDynamicRouterChange" />
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-14px">{{ t('setting.fixedMenu') }}</span>
|
||||
<ElSwitch v-model="fixedMenu" @change="fixedMenuChange" />
|
||||
|
|
|
@ -67,7 +67,7 @@ const layout = computed(() => appStore.getLayout)
|
|||
border: 2px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
|
||||
&:before {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -79,14 +79,14 @@ const layout = computed(() => appStore.getLayout)
|
|||
content: '';
|
||||
}
|
||||
|
||||
&:after {
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 25%;
|
||||
background-color: #fff;
|
||||
border-radius: 4px 4px 0 4px;
|
||||
border-radius: 4px 4px 0;
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ const layout = computed(() => appStore.getLayout)
|
|||
border: 2px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
|
||||
&:before {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -107,7 +107,7 @@ const layout = computed(() => appStore.getLayout)
|
|||
content: '';
|
||||
}
|
||||
|
||||
&:after {
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -123,7 +123,7 @@ const layout = computed(() => appStore.getLayout)
|
|||
border: 2px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
|
||||
&:before {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -140,7 +140,7 @@ const layout = computed(() => appStore.getLayout)
|
|||
border: 2px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
|
||||
&:before {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -152,7 +152,7 @@ const layout = computed(() => appStore.getLayout)
|
|||
content: '';
|
||||
}
|
||||
|
||||
&:after {
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
|
|
@ -85,6 +85,9 @@ export default defineComponent({
|
|||
} else {
|
||||
showTitle.value = !collapse
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -197,11 +200,12 @@ export default defineComponent({
|
|||
</div>
|
||||
<Menu
|
||||
class={[
|
||||
'!absolute top-0',
|
||||
'!absolute top-0 z-3000',
|
||||
{
|
||||
'!left-[var(--tab-menu-min-width)]': unref(collapse),
|
||||
'!left-[var(--tab-menu-max-width)]': !unref(collapse),
|
||||
'!w-[calc(var(--left-menu-max-width)+1px)]': unref(showMenu) || unref(fixedMenu),
|
||||
'!w-[var(--left-menu-max-width)] border-r-1 border-r-solid border-[var(--el-border-color)]':
|
||||
unref(showMenu) || unref(fixedMenu),
|
||||
'!w-0': !unref(showMenu) && !unref(fixedMenu)
|
||||
}
|
||||
]}
|
||||
|
|
|
@ -5,7 +5,9 @@ import {
|
|||
ElPagination,
|
||||
ComponentSize,
|
||||
ElTooltipProps,
|
||||
ElImage
|
||||
ElImage,
|
||||
ElEmpty,
|
||||
ElCard
|
||||
} from 'element-plus'
|
||||
import { defineComponent, PropType, ref, computed, unref, watch, onMounted } from 'vue'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
|
@ -15,8 +17,9 @@ import { set, get } from 'lodash-es'
|
|||
import { CSSProperties } from 'vue'
|
||||
import { getSlot } from '@/utils/tsxHelper'
|
||||
import TableActions from './components/TableActions.vue'
|
||||
// import Sortable from 'sortablejs'
|
||||
// import { Icon } from '@/components/Icon'
|
||||
import { createVideoViewer } from '@/components/VideoPlayer'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Table',
|
||||
|
@ -32,8 +35,6 @@ export default defineComponent({
|
|||
type: Array as PropType<TableColumn[]>,
|
||||
default: () => []
|
||||
},
|
||||
// 展开行
|
||||
// expand: propTypes.bool.def(false),
|
||||
// 是否展示分页
|
||||
pagination: {
|
||||
type: Object as PropType<Pagination>,
|
||||
|
@ -57,12 +58,16 @@ export default defineComponent({
|
|||
type: Array as PropType<Recordable[]>,
|
||||
default: () => []
|
||||
},
|
||||
// 是否自动预览
|
||||
preview: {
|
||||
// 图片自动预览字段数组
|
||||
imagePreview: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => []
|
||||
},
|
||||
// 视频自动预览字段数组
|
||||
videoPreview: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => []
|
||||
},
|
||||
// sortable: propTypes.bool.def(false),
|
||||
height: propTypes.oneOfType([Number, String]),
|
||||
maxHeight: propTypes.oneOfType([Number, String]),
|
||||
stripe: propTypes.bool.def(false),
|
||||
|
@ -186,9 +191,27 @@ export default defineComponent({
|
|||
default: 'fixed'
|
||||
},
|
||||
scrollbarAlwaysOn: propTypes.bool.def(false),
|
||||
flexible: propTypes.bool.def(false)
|
||||
flexible: propTypes.bool.def(false),
|
||||
// 自定义内容
|
||||
customContent: propTypes.bool.def(false),
|
||||
cardBodyStyle: {
|
||||
type: Object as PropType<CSSProperties>,
|
||||
default: () => ({})
|
||||
},
|
||||
cardBodyClass: {
|
||||
type: String as PropType<string>,
|
||||
default: ''
|
||||
},
|
||||
cardWrapStyle: {
|
||||
type: Object as PropType<CSSProperties>,
|
||||
default: () => ({})
|
||||
},
|
||||
cardWrapClass: {
|
||||
type: String as PropType<string>,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh', 'sortable-change'],
|
||||
emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh'],
|
||||
setup(props, { attrs, emit, slots, expose }) {
|
||||
const elTableRef = ref<ComponentRef<typeof ElTable>>()
|
||||
|
||||
|
@ -213,33 +236,6 @@ export default defineComponent({
|
|||
return propsObj
|
||||
})
|
||||
|
||||
// const sortableEl = ref()
|
||||
// 初始化拖拽
|
||||
// const initDropTable = () => {
|
||||
// const el = unref(elTableRef)?.$el.querySelector('.el-table__body tbody')
|
||||
// if (!el) return
|
||||
// if (unref(sortableEl)) unref(sortableEl).destroy()
|
||||
|
||||
// sortableEl.value = Sortable.create(el, {
|
||||
// handle: '.table-move',
|
||||
// animation: 180,
|
||||
// onEnd(e: any) {
|
||||
// emit('sortable-change', e)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
// watch(
|
||||
// () => getProps.value.sortable,
|
||||
// async (v) => {
|
||||
// await nextTick()
|
||||
// v && initDropTable()
|
||||
// },
|
||||
// {
|
||||
// immediate: true
|
||||
// }
|
||||
// )
|
||||
|
||||
const setProps = (props: TableProps = {}) => {
|
||||
mergeProps.value = Object.assign(unref(mergeProps), props)
|
||||
outsideProps.value = { ...props } as any
|
||||
|
@ -260,7 +256,7 @@ export default defineComponent({
|
|||
|
||||
const addColumn = (column: TableColumn, index?: number) => {
|
||||
const { columns } = unref(getProps)
|
||||
if (index) {
|
||||
if (index !== void 0) {
|
||||
columns.splice(index, 0, column)
|
||||
} else {
|
||||
columns.push(column)
|
||||
|
@ -283,6 +279,10 @@ export default defineComponent({
|
|||
setProps({ size })
|
||||
}
|
||||
|
||||
const confirmSetColumn = (columns: TableColumn[]) => {
|
||||
setProps({ columns })
|
||||
}
|
||||
|
||||
expose({
|
||||
setProps,
|
||||
setColumn,
|
||||
|
@ -339,11 +339,13 @@ export default defineComponent({
|
|||
const bindValue: Recordable = { ...attrs, ...unref(getProps) }
|
||||
delete bindValue.columns
|
||||
delete bindValue.data
|
||||
delete bindValue.align
|
||||
return bindValue
|
||||
})
|
||||
|
||||
const renderTreeTableColumn = (columnsChildren: TableColumn[]) => {
|
||||
const { align, headerAlign, showOverflowTooltip, preview } = unref(getProps)
|
||||
const { align, headerAlign, showOverflowTooltip, imagePreview, videoPreview } =
|
||||
unref(getProps)
|
||||
return columnsChildren.map((v) => {
|
||||
if (v.hidden) return null
|
||||
const props = { ...v } as any
|
||||
|
@ -354,24 +356,24 @@ export default defineComponent({
|
|||
const slots = {
|
||||
default: (...args: any[]) => {
|
||||
const data = args[0]
|
||||
let isImageUrl = false
|
||||
if (preview.length) {
|
||||
isImageUrl = preview.some((item) => (item as string) === v.field)
|
||||
}
|
||||
let isPreview = false
|
||||
isPreview =
|
||||
imagePreview.some((item) => (item as string) === v.field) ||
|
||||
videoPreview.some((item) => (item as string) === v.field)
|
||||
|
||||
return children && children.length
|
||||
? renderTreeTableColumn(children)
|
||||
: props?.slots?.default
|
||||
? props.slots.default(args)
|
||||
: v?.formatter
|
||||
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
|
||||
: isImageUrl
|
||||
? renderPreview(get(data.row, v.field))
|
||||
: get(data.row, v.field)
|
||||
? props.slots.default(...args)
|
||||
: v?.formatter
|
||||
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
|
||||
: isPreview
|
||||
? renderPreview(get(data.row, v.field), v.field)
|
||||
: get(data.row, v.field)
|
||||
}
|
||||
}
|
||||
if (props?.slots?.header) {
|
||||
slots['header'] = (...args: any[]) => props.slots.header(args)
|
||||
slots['header'] = (...args: any[]) => props.slots.header(...args)
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -388,17 +390,32 @@ export default defineComponent({
|
|||
})
|
||||
}
|
||||
|
||||
const renderPreview = (url: string) => {
|
||||
const renderPreview = (url: string, field: string) => {
|
||||
const { imagePreview, videoPreview } = unref(getProps)
|
||||
return (
|
||||
<div class="flex items-center">
|
||||
<ElImage
|
||||
src={url}
|
||||
fit="cover"
|
||||
class="w-[100%] h-100px"
|
||||
lazy
|
||||
preview-src-list={[url]}
|
||||
preview-teleported
|
||||
/>
|
||||
{imagePreview.includes(field) ? (
|
||||
<ElImage
|
||||
src={url}
|
||||
fit="cover"
|
||||
class="w-[100%]"
|
||||
lazy
|
||||
preview-src-list={[url]}
|
||||
preview-teleported
|
||||
/>
|
||||
) : videoPreview.includes(field) ? (
|
||||
<BaseButton
|
||||
type="primary"
|
||||
icon={<Icon icon="ep:video-play" />}
|
||||
onClick={() => {
|
||||
createVideoViewer({
|
||||
url
|
||||
})
|
||||
}}
|
||||
>
|
||||
预览
|
||||
</BaseButton>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -413,7 +430,8 @@ export default defineComponent({
|
|||
headerAlign,
|
||||
showOverflowTooltip,
|
||||
reserveSelection,
|
||||
preview
|
||||
imagePreview,
|
||||
videoPreview
|
||||
} = unref(getProps)
|
||||
|
||||
return (columnsChildren || columns).map((v) => {
|
||||
|
@ -428,6 +446,7 @@ export default defineComponent({
|
|||
align={v.align || align}
|
||||
headerAlign={v.headerAlign || headerAlign}
|
||||
label={v.label}
|
||||
fixed={v.fixed}
|
||||
width="65px"
|
||||
></ElTableColumn>
|
||||
)
|
||||
|
@ -438,6 +457,7 @@ export default defineComponent({
|
|||
reserveSelection={reserveSelection}
|
||||
align={align}
|
||||
headerAlign={headerAlign}
|
||||
selectable={v.selectable}
|
||||
width="50"
|
||||
></ElTableColumn>
|
||||
)
|
||||
|
@ -451,24 +471,24 @@ export default defineComponent({
|
|||
default: (...args: any[]) => {
|
||||
const data = args[0]
|
||||
|
||||
let isImageUrl = false
|
||||
if (preview.length) {
|
||||
isImageUrl = preview.some((item) => (item as string) === v.field)
|
||||
}
|
||||
let isPreview = false
|
||||
isPreview =
|
||||
imagePreview.some((item) => (item as string) === v.field) ||
|
||||
videoPreview.some((item) => (item as string) === v.field)
|
||||
|
||||
return children && children.length
|
||||
? renderTreeTableColumn(children)
|
||||
: props?.slots?.default
|
||||
? props.slots.default(args)
|
||||
: v?.formatter
|
||||
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
|
||||
: isImageUrl
|
||||
? renderPreview(get(data.row, v.field))
|
||||
: get(data.row, v.field)
|
||||
? props.slots.default(...args)
|
||||
: v?.formatter
|
||||
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
|
||||
: isPreview
|
||||
? renderPreview(get(data.row, v.field), v.field)
|
||||
: get(data.row, v.field)
|
||||
}
|
||||
}
|
||||
if (props?.slots?.header) {
|
||||
slots['header'] = (...args: any[]) => props.slots.header(args)
|
||||
slots['header'] = (...args: any[]) => props.slots.header(...args)
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -495,35 +515,63 @@ export default defineComponent({
|
|||
tableSlots['append'] = (...args: any[]) => getSlot(slots, 'append', args)
|
||||
}
|
||||
|
||||
// const { sortable } = unref(getProps)
|
||||
|
||||
// const sortableEl = sortable ? (
|
||||
// <ElTableColumn
|
||||
// className="table-move cursor-move"
|
||||
// type="sortable"
|
||||
// prop="sortable"
|
||||
// width="60px"
|
||||
// align="center"
|
||||
// >
|
||||
// <Icon icon="ant-design:drag-outlined" />
|
||||
// </ElTableColumn>
|
||||
// ) : null
|
||||
|
||||
return (
|
||||
<div v-loading={unref(getProps).loading}>
|
||||
{unref(getProps).showAction ? (
|
||||
<TableActions
|
||||
columns={unref(getProps).columns}
|
||||
onChangSize={changSize}
|
||||
onRefresh={refresh}
|
||||
/>
|
||||
) : null}
|
||||
<ElTable ref={elTableRef} data={unref(getProps).data} {...unref(getBindValue)}>
|
||||
{{
|
||||
default: () => renderTableColumn(),
|
||||
...tableSlots
|
||||
}}
|
||||
</ElTable>
|
||||
{unref(getProps).customContent ? (
|
||||
<div class="flex flex-wrap">
|
||||
{unref(getProps)?.data?.length ? (
|
||||
unref(getProps)?.data.map((item) => {
|
||||
const cardSlots = {
|
||||
default: () => {
|
||||
return getSlot(slots, 'content', item)
|
||||
}
|
||||
}
|
||||
if (getSlot(slots, 'content-header')) {
|
||||
cardSlots['header'] = () => {
|
||||
return getSlot(slots, 'content-header', item)
|
||||
}
|
||||
}
|
||||
if (getSlot(slots, 'content-footer')) {
|
||||
cardSlots['footer'] = () => {
|
||||
return getSlot(slots, 'content-footer', item)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<ElCard
|
||||
shadow="hover"
|
||||
class={unref(getProps).cardWrapClass}
|
||||
style={unref(getProps).cardWrapStyle}
|
||||
bodyClass={unref(getProps).cardBodyClass}
|
||||
bodyStyle={unref(getProps).cardBodyStyle}
|
||||
>
|
||||
{cardSlots}
|
||||
</ElCard>
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<div class="flex flex-1 justify-center">
|
||||
<ElEmpty description="暂无数据" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{unref(getProps).showAction && !unref(getProps).customContent ? (
|
||||
<TableActions
|
||||
columns={unref(getProps).columns}
|
||||
onChangSize={changSize}
|
||||
onRefresh={refresh}
|
||||
onConfirm={confirmSetColumn}
|
||||
/>
|
||||
) : null}
|
||||
<ElTable ref={elTableRef} data={unref(getProps).data} {...unref(getBindValue)}>
|
||||
{{
|
||||
default: () => renderTableColumn(),
|
||||
...tableSlots
|
||||
}}
|
||||
</ElTable>
|
||||
</>
|
||||
)}
|
||||
{unref(getProps).pagination ? (
|
||||
<ElPagination
|
||||
v-model:pageSize={pageSizeRef.value}
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
ElDrawer,
|
||||
ElCheckbox,
|
||||
ElCheckboxGroup,
|
||||
ElText,
|
||||
ElRadioButton,
|
||||
ElRadioGroup
|
||||
} from 'element-plus'
|
||||
import { TableColumn } from '../types'
|
||||
import { PropType, ref, watch, unref } from 'vue'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { DEFAULT_FILTER_COLUMN } from '@/constants'
|
||||
import { VueDraggable } from 'vue-draggable-plus'
|
||||
|
||||
const modelValue = defineModel<boolean>()
|
||||
|
||||
const props = defineProps({
|
||||
columns: {
|
||||
type: Array as PropType<TableColumn[]>,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['confirm'])
|
||||
|
||||
const oldColumns = ref<TableColumn[]>()
|
||||
|
||||
const settingColumns = ref<TableColumn[]>()
|
||||
|
||||
// 存储不要的列
|
||||
const hiddenColumns = ref<TableColumn[]>([])
|
||||
|
||||
const defaultCheckColumns = ref<string[]>([])
|
||||
const checkColumns = ref<string[]>([])
|
||||
|
||||
const checkAll = ref(false)
|
||||
const isIndeterminate = ref(true)
|
||||
const handleCheckAllChange = (val: boolean) => {
|
||||
checkColumns.value = val ? unref(defaultCheckColumns) : []
|
||||
isIndeterminate.value = false
|
||||
}
|
||||
|
||||
const handleCheckedColumnsChange = (value: string[]) => {
|
||||
const checkedCount = value.length
|
||||
checkAll.value = checkedCount === unref(defaultCheckColumns)?.length
|
||||
isIndeterminate.value = checkedCount > 0 && checkedCount < unref(defaultCheckColumns)?.length
|
||||
}
|
||||
|
||||
const confirm = () => {
|
||||
const newColumns = cloneDeep(unref(settingColumns))?.map((item) => {
|
||||
const fixed = unref(settingColumns)?.find((col) => col.field === item.field)?.fixed
|
||||
item.hidden = !!!unref(checkColumns)?.includes(item.field)
|
||||
item.fixed = fixed ? fixed : undefined
|
||||
return item
|
||||
})
|
||||
emit('confirm', [...unref(hiddenColumns), ...(newColumns || [])])
|
||||
modelValue.value = false
|
||||
}
|
||||
|
||||
const restore = () => {
|
||||
initColumns([...unref(hiddenColumns), ...(unref(oldColumns) || [])], true)
|
||||
}
|
||||
|
||||
const initColumns = (columns: TableColumn[], isReStore = false) => {
|
||||
const newColumns = columns?.filter((item) => {
|
||||
if (!isReStore) {
|
||||
item.fixed = item.fixed !== void 0 ? item.fixed : undefined
|
||||
}
|
||||
return (item.type && !DEFAULT_FILTER_COLUMN.includes(item.type)) || !item.type
|
||||
})
|
||||
if (!unref(oldColumns)?.length) {
|
||||
oldColumns.value = cloneDeep(newColumns)
|
||||
}
|
||||
settingColumns.value = cloneDeep(newColumns)
|
||||
|
||||
hiddenColumns.value = cloneDeep(
|
||||
columns?.filter((item) => item.type && DEFAULT_FILTER_COLUMN.includes(item.type))
|
||||
)
|
||||
|
||||
defaultCheckColumns.value = unref(settingColumns)?.map((item) => item.field) || []
|
||||
checkColumns.value =
|
||||
unref(settingColumns)
|
||||
?.filter((item) => !item.hidden)
|
||||
?.map((item) => item.field) || []
|
||||
|
||||
if (unref(checkColumns)?.length === unref(defaultCheckColumns)?.length) {
|
||||
checkAll.value = true
|
||||
isIndeterminate.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.columns,
|
||||
(columns) => {
|
||||
initColumns(columns)
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElDrawer v-model="modelValue" title="列设置" size="350px">
|
||||
<div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center justify-between">
|
||||
<ElCheckbox
|
||||
v-model="checkAll"
|
||||
:indeterminate="isIndeterminate"
|
||||
@change="handleCheckAllChange"
|
||||
/>
|
||||
<ElText class="ml-8px!">{{ checkColumns.length }} / {{ settingColumns?.length }}</ElText>
|
||||
</div>
|
||||
<ElText>固定 / 排序</ElText>
|
||||
</div>
|
||||
<div v-if="settingColumns?.length">
|
||||
<VueDraggable
|
||||
v-model="settingColumns"
|
||||
target=".el-checkbox-group"
|
||||
handle=".handle"
|
||||
:animation="150"
|
||||
>
|
||||
<ElCheckboxGroup
|
||||
ref="draggableWrap"
|
||||
v-model="checkColumns"
|
||||
@change="handleCheckedColumnsChange"
|
||||
>
|
||||
<div
|
||||
v-for="item in settingColumns"
|
||||
:key="item.field"
|
||||
class="flex items-center justify-between mt-12px"
|
||||
>
|
||||
<ElCheckbox :label="item.field">
|
||||
{{ item.label }}
|
||||
</ElCheckbox>
|
||||
<div class="flex items-center">
|
||||
<ElRadioGroup size="small" v-model="item.fixed">
|
||||
<ElRadioButton label="left">
|
||||
<Icon icon="ep:arrow-left" />
|
||||
</ElRadioButton>
|
||||
<ElRadioButton :label="undefined">
|
||||
<Icon icon="ep:close" />
|
||||
</ElRadioButton>
|
||||
<ElRadioButton label="right">
|
||||
<Icon icon="ep:arrow-right" />
|
||||
</ElRadioButton>
|
||||
</ElRadioGroup>
|
||||
|
||||
<div class="ml-12px cursor-move handle"><Icon icon="ep:rank" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</ElCheckboxGroup>
|
||||
</VueDraggable>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div>
|
||||
<BaseButton @click="restore">还原</BaseButton>
|
||||
<BaseButton type="primary" @click="confirm">确定</BaseButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDrawer>
|
||||
</template>
|
|
@ -1,36 +1,30 @@
|
|||
<script lang="tsx">
|
||||
import { defineComponent, unref, computed, PropType, watch } from 'vue'
|
||||
import {
|
||||
ElTooltip,
|
||||
ElDropdown,
|
||||
ElDropdownMenu,
|
||||
ElDropdownItem,
|
||||
ComponentSize
|
||||
// ElPopover,
|
||||
// ElTree
|
||||
} from 'element-plus'
|
||||
import { defineComponent, unref, computed, PropType, ref } from 'vue'
|
||||
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ComponentSize } from 'element-plus'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { TableColumn } from '../types'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
// import { eachTree } from '@/utils/tree'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const sizeMap = computed(() => appStore.sizeMap)
|
||||
|
||||
const { t } = useI18n()
|
||||
import ColumnSetting from './ColumnSetting.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TableActions',
|
||||
components: {
|
||||
ColumnSetting
|
||||
},
|
||||
props: {
|
||||
columns: {
|
||||
type: Array as PropType<TableColumn[]>,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
emits: ['refresh', 'changSize'],
|
||||
emits: ['refresh', 'changSize', 'confirm'],
|
||||
setup(props, { emit }) {
|
||||
const appStore = useAppStore()
|
||||
const { t } = useI18n()
|
||||
const sizeMap = computed(() => appStore.sizeMap)
|
||||
const showSetting = ref(false)
|
||||
|
||||
const refresh = () => {
|
||||
emit('refresh')
|
||||
}
|
||||
|
@ -39,111 +33,71 @@ export default defineComponent({
|
|||
emit('changSize', size)
|
||||
}
|
||||
|
||||
const columns = computed(() => {
|
||||
return cloneDeep(props.columns).filter((v) => {
|
||||
// 去掉type为selection的列和expand的列
|
||||
if (v.type !== 'selection' && v.type !== 'expand') {
|
||||
return v
|
||||
}
|
||||
})
|
||||
})
|
||||
const confirm = (columns: TableColumn[]) => {
|
||||
emit('confirm', columns)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => columns.value,
|
||||
(newColumns) => {
|
||||
console.log('columns change:', newColumns)
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
const showColumnSetting = () => {
|
||||
showSetting.value = true
|
||||
}
|
||||
|
||||
return () => (
|
||||
<>
|
||||
<div class="text-right h-28px flex items-center justify-end">
|
||||
<ElTooltip content={t('common.refresh')} placement="top">
|
||||
<span onClick={refresh}>
|
||||
<Icon
|
||||
icon="ant-design:sync-outlined"
|
||||
class="cursor-pointer"
|
||||
hover-color="var(--el-color-primary)"
|
||||
/>
|
||||
</span>
|
||||
</ElTooltip>
|
||||
<div title="刷新" class="w-30px h-20px flex items-center justify-end" onClick={refresh}>
|
||||
<Icon
|
||||
icon="ant-design:sync-outlined"
|
||||
class="cursor-pointer"
|
||||
hover-color="var(--el-color-primary)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ElTooltip content={t('common.size')} placement="top">
|
||||
<ElDropdown trigger="click" onCommand={changSize}>
|
||||
{{
|
||||
default: () => {
|
||||
return (
|
||||
<span>
|
||||
<Icon
|
||||
icon="ant-design:column-height-outlined"
|
||||
class="cursor-pointer mr-8px ml-8px"
|
||||
hover-color="var(--el-color-primary)"
|
||||
/>
|
||||
</span>
|
||||
)
|
||||
},
|
||||
dropdown: () => {
|
||||
return (
|
||||
<ElDropdownMenu>
|
||||
{{
|
||||
default: () => {
|
||||
return unref(sizeMap).map((v) => {
|
||||
return (
|
||||
<ElDropdownItem key={v} command={v}>
|
||||
{t(`size.${v}`)}
|
||||
</ElDropdownItem>
|
||||
)
|
||||
})
|
||||
}
|
||||
}}
|
||||
</ElDropdownMenu>
|
||||
)
|
||||
}
|
||||
}}
|
||||
</ElDropdown>
|
||||
</ElTooltip>
|
||||
|
||||
{/* <ElTooltip content={t('common.columnSetting')} placement="top"> */}
|
||||
{/* <ElPopover trigger="click" placement="left">
|
||||
<ElDropdown trigger="click" onCommand={changSize}>
|
||||
{{
|
||||
default: () => {
|
||||
return (
|
||||
<div>
|
||||
<ElTree
|
||||
data={unref(columns)}
|
||||
show-checkbox
|
||||
default-checked-keys={unref(defaultCheckeds)}
|
||||
draggable
|
||||
node-key="field"
|
||||
allow-drop={(_draggingNode: any, _dropNode: any, type: string) => {
|
||||
if (type === 'inner') {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}}
|
||||
onNode-drag-end={onNodeDragEnd}
|
||||
onCheck-change={onCheckChange}
|
||||
<div title="尺寸" class="w-30px h-20px flex items-center justify-end">
|
||||
<Icon
|
||||
icon="ant-design:column-height-outlined"
|
||||
class="cursor-pointer"
|
||||
hover-color="var(--el-color-primary)"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
reference: () => {
|
||||
dropdown: () => {
|
||||
return (
|
||||
<Icon
|
||||
icon="ant-design:setting-outlined"
|
||||
class="cursor-pointer"
|
||||
hoverColor="var(--el-color-primary)"
|
||||
/>
|
||||
<ElDropdownMenu>
|
||||
{{
|
||||
default: () => {
|
||||
return unref(sizeMap).map((v) => {
|
||||
return (
|
||||
<ElDropdownItem key={v} command={v}>
|
||||
{t(`size.${v}`)}
|
||||
</ElDropdownItem>
|
||||
)
|
||||
})
|
||||
}
|
||||
}}
|
||||
</ElDropdownMenu>
|
||||
)
|
||||
}
|
||||
}}
|
||||
</ElPopover> */}
|
||||
{/* </ElTooltip> */}
|
||||
</ElDropdown>
|
||||
|
||||
<div
|
||||
title="列设置"
|
||||
class="w-30px h-20px flex items-center justify-end"
|
||||
onClick={showColumnSetting}
|
||||
>
|
||||
<Icon
|
||||
icon="ant-design:setting-outlined"
|
||||
class="cursor-pointer"
|
||||
hover-color="var(--el-color-primary)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ColumnSetting v-model={showSetting.value} columns={props.columns} onConfirm={confirm} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ import { useDesign } from '@/hooks/web/useDesign'
|
|||
import { useTemplateRefsList } from '@vueuse/core'
|
||||
import { ElScrollbar } from 'element-plus'
|
||||
import { useScrollTo } from '@/hooks/event/useScrollTo'
|
||||
import { useTagsView } from '@/hooks/web/useTagsView'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
|
@ -19,7 +21,9 @@ const prefixCls = getPrefixCls('tags-view')
|
|||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { currentRoute, push, replace } = useRouter()
|
||||
const { currentRoute, push } = useRouter()
|
||||
|
||||
const { closeAll, closeLeft, closeRight, closeOther, closeCurrent, refreshPage } = useTagsView()
|
||||
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
|
@ -31,6 +35,10 @@ const visitedViews = computed(() => tagsViewStore.getVisitedViews)
|
|||
|
||||
const affixTagArr = ref<RouteLocationNormalizedLoaded[]>([])
|
||||
|
||||
const selectedTag = computed(() => tagsViewStore.getSelectedTag)
|
||||
|
||||
const setSelectTag = tagsViewStore.setSelectedTag
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const tagsViewIcon = computed(() => appStore.getTagsViewIcon)
|
||||
|
@ -43,66 +51,30 @@ const initTags = () => {
|
|||
for (const tag of unref(affixTagArr)) {
|
||||
// Must have tag name
|
||||
if (tag.name) {
|
||||
tagsViewStore.addVisitedView(tag)
|
||||
tagsViewStore.addVisitedView(cloneDeep(tag))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const selectedTag = ref<RouteLocationNormalizedLoaded>()
|
||||
|
||||
// 新增tag
|
||||
const addTags = () => {
|
||||
const { name } = unref(currentRoute)
|
||||
if (name) {
|
||||
selectedTag.value = unref(currentRoute)
|
||||
setSelectTag(unref(currentRoute))
|
||||
tagsViewStore.addView(unref(currentRoute))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 关闭选中的tag
|
||||
const closeSelectedTag = (view: RouteLocationNormalizedLoaded) => {
|
||||
if (view?.meta?.affix) return
|
||||
tagsViewStore.delView(view)
|
||||
if (isActive(view)) {
|
||||
toLastView()
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭全部
|
||||
const closeAllTags = () => {
|
||||
tagsViewStore.delAllViews()
|
||||
toLastView()
|
||||
}
|
||||
|
||||
// 关闭其它
|
||||
const closeOthersTags = () => {
|
||||
tagsViewStore.delOthersViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||
}
|
||||
|
||||
// 重新加载
|
||||
const refreshSelectedTag = async (view?: RouteLocationNormalizedLoaded) => {
|
||||
if (!view) return
|
||||
tagsViewStore.delCachedView()
|
||||
const { path, query } = view
|
||||
await nextTick()
|
||||
replace({
|
||||
path: '/redirect' + path,
|
||||
query: query
|
||||
closeCurrent(view, () => {
|
||||
if (isActive(view)) {
|
||||
toLastView()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 关闭左侧
|
||||
const closeLeftTags = () => {
|
||||
tagsViewStore.delLeftViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||
}
|
||||
|
||||
// 关闭右侧
|
||||
const closeRightTags = () => {
|
||||
tagsViewStore.delRightViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||
}
|
||||
|
||||
// 跳转到最后一个
|
||||
// 去最后一个
|
||||
const toLastView = () => {
|
||||
const visitedViews = tagsViewStore.getVisitedViews
|
||||
const latestView = visitedViews.slice(-1)[0]
|
||||
|
@ -121,6 +93,33 @@ const toLastView = () => {
|
|||
}
|
||||
}
|
||||
|
||||
// 关闭全部
|
||||
const closeAllTags = () => {
|
||||
closeAll(() => {
|
||||
toLastView()
|
||||
})
|
||||
}
|
||||
|
||||
// 关闭其它
|
||||
const closeOthersTags = () => {
|
||||
closeOther()
|
||||
}
|
||||
|
||||
// 重新加载
|
||||
const refreshSelectedTag = async (view?: RouteLocationNormalizedLoaded) => {
|
||||
refreshPage(view)
|
||||
}
|
||||
|
||||
// 关闭左侧
|
||||
const closeLeftTags = () => {
|
||||
closeLeft()
|
||||
}
|
||||
|
||||
// 关闭右侧
|
||||
const closeRightTags = () => {
|
||||
closeRight()
|
||||
}
|
||||
|
||||
// 滚动到选中的tag
|
||||
const moveToCurrentTag = async () => {
|
||||
await nextTick()
|
||||
|
@ -211,13 +210,14 @@ const isActive = (route: RouteLocationNormalizedLoaded): boolean => {
|
|||
// 所有右键菜单组件的元素
|
||||
const itemRefs = useTemplateRefsList<ComponentRef<typeof ContextMenu & ContextMenuExpose>>()
|
||||
|
||||
// 右键菜单装填改变的时候
|
||||
// 右键菜单状态改变的时候
|
||||
const visibleChange = (visible: boolean, tagItem: RouteLocationNormalizedLoaded) => {
|
||||
if (visible) {
|
||||
for (const v of unref(itemRefs)) {
|
||||
const elDropdownMenuRef = v.elDropdownMenuRef
|
||||
if (tagItem.fullPath !== v.tagItem.fullPath) {
|
||||
elDropdownMenuRef?.handleClose()
|
||||
setSelectTag(tagItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import { useDesign } from '@/hooks/web/useDesign'
|
|||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const emit = defineEmits(['change'])
|
||||
|
||||
const prefixCls = getPrefixCls('theme-switch')
|
||||
|
||||
const Sun = useIcon({ icon: 'emojione-monotone:sun', color: '#fde047' })
|
||||
|
@ -23,6 +25,7 @@ const blackColor = 'var(--el-color-black)'
|
|||
|
||||
const themeChange = (val: boolean) => {
|
||||
appStore.setIsDark(val)
|
||||
emit('change', val)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,49 +1,27 @@
|
|||
<script setup lang="ts">
|
||||
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ElMessageBox } from 'element-plus'
|
||||
import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
import { resetRouter } from '@/router'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { loginOutApi } from '@/api/login'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import LockDialog from './components/LockDialog.vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import LockPage from './components/LockPage.vue'
|
||||
import { useLockStore } from '@/store/modules/lock'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const lockStore = useLockStore()
|
||||
|
||||
const getIsLock = computed(() => lockStore.getLockInfo?.isLock ?? false)
|
||||
|
||||
const tagsViewStore = useTagsViewStore()
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('user-info')
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { clear } = useStorage()
|
||||
|
||||
const { replace } = useRouter()
|
||||
|
||||
const loginOut = () => {
|
||||
ElMessageBox.confirm(t('common.loginOutMessage'), t('common.reminder'), {
|
||||
confirmButtonText: t('common.ok'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
const res = await loginOutApi().catch(() => {})
|
||||
if (res) {
|
||||
clear()
|
||||
tagsViewStore.delAllViews()
|
||||
resetRouter() // 重置静态路由表
|
||||
replace('/login')
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
userStore.logoutConfirm()
|
||||
}
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
|
@ -66,7 +44,9 @@ const toDocument = () => {
|
|||
alt=""
|
||||
class="w-[calc(var(--logo-height)-25px)] rounded-[50%]"
|
||||
/>
|
||||
<span class="<lg:hidden text-14px pl-[5px] text-[var(--top-header-text-color)]">Archer</span>
|
||||
<span class="<lg:hidden text-14px pl-[5px] text-[var(--top-header-text-color)]">{{
|
||||
userStore.getUserInfo?.username
|
||||
}}</span>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<ElDropdownMenu>
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import { VNode, createVNode, render } from 'vue'
|
||||
import VideoPlayer from './src/VideoPlayer.vue'
|
||||
import { isClient } from '@/utils/is'
|
||||
import { VideoPlayerViewer } from '@/components/VideoPlayerViewer'
|
||||
import { toAnyString } from '@/utils'
|
||||
|
||||
export { VideoPlayer }
|
||||
|
||||
let instance: Nullable<VNode> = null
|
||||
|
||||
export function createVideoViewer(options: { url: string; poster?: string; show?: boolean }) {
|
||||
if (!isClient) return
|
||||
const { url, poster } = options
|
||||
|
||||
const propsData: Partial<{ url: string; poster?: string; show?: boolean; id?: string }> = {}
|
||||
const container = document.createElement('div')
|
||||
const id = toAnyString()
|
||||
container.id = id
|
||||
propsData.url = url
|
||||
propsData.poster = poster
|
||||
propsData.show = true
|
||||
propsData.id = id
|
||||
|
||||
document.body.appendChild(container)
|
||||
instance = createVNode(VideoPlayerViewer, propsData)
|
||||
render(instance, container)
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<script setup lang="ts">
|
||||
import Player from 'xgplayer'
|
||||
import { ref, unref, onMounted, watch, onBeforeUnmount, nextTick } from 'vue'
|
||||
import 'xgplayer/dist/index.min.css'
|
||||
|
||||
const props = defineProps({
|
||||
url: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true
|
||||
},
|
||||
poster: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const playerRef = ref<Player>()
|
||||
|
||||
const videoEl = ref<HTMLDivElement>()
|
||||
|
||||
const intiPlayer = () => {
|
||||
if (!unref(videoEl)) return
|
||||
new Player({
|
||||
autoplay: false,
|
||||
...props,
|
||||
el: unref(videoEl)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
intiPlayer()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props,
|
||||
async (newProps) => {
|
||||
await nextTick()
|
||||
if (newProps) {
|
||||
unref(playerRef)?.setConfig(newProps)
|
||||
}
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
unref(playerRef)?.destroy()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
playerExpose: () => unref(playerRef)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="videoEl"></div>
|
||||
</template>
|
|
@ -0,0 +1,3 @@
|
|||
import VideoPlayerViewer from './src/VideoPlayerViewer.vue'
|
||||
|
||||
export { VideoPlayerViewer }
|
|
@ -0,0 +1,49 @@
|
|||
<script setup lang="ts">
|
||||
import { VideoPlayer } from '@/components/VideoPlayer'
|
||||
import { ElOverlay } from 'element-plus'
|
||||
import { ref, nextTick } from 'vue'
|
||||
import { Icon } from '@/components/Icon'
|
||||
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true
|
||||
},
|
||||
poster: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const visible = ref(props.show)
|
||||
|
||||
const close = async () => {
|
||||
visible.value = false
|
||||
await nextTick()
|
||||
const wrap = document.getElementById(props.id)
|
||||
if (!wrap) return
|
||||
document.body.removeChild(wrap)
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<ElOverlay v-show="visible" @click="close">
|
||||
<div class="w-full h-full flex justify-center items-center relative" @click="close">
|
||||
<div
|
||||
class="w-44px h-44px color-[#fff] bg-[var(--el-text-color-regular)] rounded-full border-[#fff] flex justify-center items-center cursor-pointer absolute top-40px right-40px"
|
||||
@click="close"
|
||||
>
|
||||
<Icon icon="ep:close" :size="24" />
|
||||
</div>
|
||||
<VideoPlayer :url="url" :poster="poster" />
|
||||
</div>
|
||||
</ElOverlay>
|
||||
</template>
|
|
@ -1,6 +1,8 @@
|
|||
import type { App } from 'vue'
|
||||
import { Icon } from './Icon'
|
||||
import { BaseButton } from './Button'
|
||||
|
||||
export const setupGlobCom = (app: App<Element>): void => {
|
||||
app.component('Icon', Icon)
|
||||
app.component('BaseButton', BaseButton)
|
||||
}
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
import {
|
||||
AxiosConfig,
|
||||
AxiosResponse,
|
||||
AxiosRequestHeaders,
|
||||
AxiosError,
|
||||
InternalAxiosRequestConfig
|
||||
} from './types'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import qs from 'qs'
|
||||
|
||||
const config: AxiosConfig = {
|
||||
/**
|
||||
* api请求基础路径
|
||||
*/
|
||||
baseUrl: {
|
||||
// 开发环境接口前缀
|
||||
base: '',
|
||||
|
||||
// 打包开发环境接口前缀
|
||||
dev: '',
|
||||
|
||||
// 打包生产环境接口前缀
|
||||
pro: '',
|
||||
|
||||
// 打包测试环境接口前缀
|
||||
test: ''
|
||||
},
|
||||
|
||||
/**
|
||||
* 接口成功返回状态码
|
||||
*/
|
||||
code: 0,
|
||||
|
||||
/**
|
||||
* 接口请求超时时间
|
||||
*/
|
||||
timeout: 60000,
|
||||
|
||||
/**
|
||||
* 默认接口请求类型
|
||||
* 可选值:application/x-www-form-urlencoded multipart/form-data
|
||||
*/
|
||||
defaultHeaders: 'application/json',
|
||||
|
||||
interceptors: {
|
||||
//请求拦截
|
||||
// requestInterceptors: (config) => {
|
||||
// return config
|
||||
// },
|
||||
// 响应拦截器
|
||||
// responseInterceptors: (result: AxiosResponse) => {
|
||||
// return result
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
const defaultRequestInterceptors = (config: InternalAxiosRequestConfig) => {
|
||||
if (
|
||||
config.method === 'post' &&
|
||||
(config.headers as AxiosRequestHeaders)['Content-Type'] === 'application/x-www-form-urlencoded'
|
||||
) {
|
||||
config.data = qs.stringify(config.data)
|
||||
}
|
||||
if (config.method === 'get' && config.params) {
|
||||
let url = config.url as string
|
||||
url += '?'
|
||||
const keys = Object.keys(config.params)
|
||||
for (const key of keys) {
|
||||
if (config.params[key] !== void 0 && config.params[key] !== null) {
|
||||
url += `${key}=${encodeURIComponent(config.params[key])}&`
|
||||
}
|
||||
}
|
||||
url = url.substring(0, url.length - 1)
|
||||
config.params = {}
|
||||
config.url = url
|
||||
}
|
||||
return config
|
||||
}
|
||||
;(error: AxiosError) => {
|
||||
console.log(error)
|
||||
Promise.reject(error)
|
||||
}
|
||||
|
||||
const defaultResponseInterceptors = (response: AxiosResponse<any>) => {
|
||||
if (response?.config?.responseType === 'blob') {
|
||||
// 如果是文件流,直接过
|
||||
return response
|
||||
} else if (response.data.code === config.code) {
|
||||
return response.data
|
||||
} else {
|
||||
ElMessage.error(response.data.message)
|
||||
}
|
||||
}
|
||||
;(error: AxiosError) => {
|
||||
console.log('err' + error) // for debug
|
||||
ElMessage.error(error.message)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
export { defaultResponseInterceptors, defaultRequestInterceptors }
|
||||
export default config
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* 请求成功状态码
|
||||
*/
|
||||
export const SUCCESS_CODE = 0
|
||||
|
||||
/**
|
||||
* 请求contentType
|
||||
*/
|
||||
export const CONTENT_TYPE: AxiosContentType = 'application/json'
|
||||
|
||||
/**
|
||||
* 请求超时时间
|
||||
*/
|
||||
export const REQUEST_TIMEOUT = 60000
|
||||
|
||||
/**
|
||||
* 不重定向白名单
|
||||
*/
|
||||
export const NO_REDIRECT_WHITE_LIST = ['/login']
|
||||
|
||||
/**
|
||||
* 不重置路由白名单
|
||||
*/
|
||||
export const NO_RESET_WHITE_LIST = ['Redirect', 'Login', 'NoFind', 'Root']
|
||||
|
||||
/**
|
||||
* 表格默认过滤列设置字段
|
||||
*/
|
||||
export const DEFAULT_FILTER_COLUMN = ['expand', 'selection']
|
||||
|
||||
/**
|
||||
* 是否根据headers->content-type自动转换数据格式
|
||||
*/
|
||||
export const TRANSFORM_REQUEST_DATA = true
|
|
@ -1,10 +0,0 @@
|
|||
import type { App } from 'vue'
|
||||
import { setupPermissionDirective } from './permission/hasPermi'
|
||||
|
||||
/**
|
||||
* 导出指令:v-xxx
|
||||
* @methods hasPermi 按钮权限,用法: v-hasPermi
|
||||
*/
|
||||
export const setupPermission = (app: App<Element>) => {
|
||||
setupPermissionDirective(app)
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
import type { App, Directive, DirectiveBinding } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
import { intersection } from 'lodash-es'
|
||||
import { isArray } from '@/utils/is'
|
||||
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { getStorage } = useStorage()
|
||||
const appStore = useAppStoreWithOut()
|
||||
|
||||
// 全部权限
|
||||
const all_permission = ['*.*.*']
|
||||
const hasPermission = (value: string | string[]): boolean => {
|
||||
const permissions = getStorage(appStore.getUserInfo).permissions as string[]
|
||||
if (!value) {
|
||||
throw new Error(t('permission.hasPermission'))
|
||||
}
|
||||
if (!isArray(value)) {
|
||||
return permissions?.includes(value as string)
|
||||
}
|
||||
if (all_permission[0] === permissions[0]) {
|
||||
return true
|
||||
}
|
||||
return (intersection(value, permissions) as string[]).length > 0
|
||||
}
|
||||
function hasPermi(el: Element, binding: DirectiveBinding) {
|
||||
const value = binding.value
|
||||
|
||||
const flag = hasPermission(value)
|
||||
if (!flag) {
|
||||
el.parentNode?.removeChild(el)
|
||||
}
|
||||
}
|
||||
const mounted = (el: Element, binding: DirectiveBinding<any>) => {
|
||||
hasPermi(el, binding)
|
||||
}
|
||||
|
||||
const permiDirective: Directive = {
|
||||
mounted
|
||||
}
|
||||
|
||||
export const setupPermissionDirective = (app: App<Element>) => {
|
||||
app.directive('hasPermi', permiDirective)
|
||||
}
|
||||
|
||||
export default permiDirective
|
|
@ -78,20 +78,14 @@ const filterSearchSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
|
|||
|
||||
for (let i = 0; i < length; i++) {
|
||||
const schemaItem = crudSchema[i]
|
||||
// 判断是否隐藏
|
||||
if (!schemaItem?.search?.hidden) {
|
||||
const searchSchemaItem = {
|
||||
component: schemaItem?.search?.component || 'Input',
|
||||
...schemaItem.search,
|
||||
field: schemaItem.field,
|
||||
label: schemaItem.label
|
||||
}
|
||||
|
||||
// 删除不必要的字段
|
||||
delete searchSchemaItem.hidden
|
||||
|
||||
searchSchema.push(searchSchemaItem)
|
||||
const searchSchemaItem = {
|
||||
component: schemaItem?.search?.component || 'Input',
|
||||
...schemaItem.search,
|
||||
field: schemaItem.field,
|
||||
label: schemaItem.label
|
||||
}
|
||||
|
||||
searchSchema.push(searchSchemaItem)
|
||||
}
|
||||
|
||||
return searchSchema
|
||||
|
@ -127,19 +121,14 @@ const filterFormSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
|
|||
for (let i = 0; i < length; i++) {
|
||||
const formItem = crudSchema[i]
|
||||
// 判断是否隐藏
|
||||
if (!formItem?.form?.hidden) {
|
||||
const formSchemaItem = {
|
||||
component: formItem?.form?.component || 'Input',
|
||||
...formItem.form,
|
||||
field: formItem.field,
|
||||
label: formItem.label
|
||||
}
|
||||
|
||||
// 删除不必要的字段
|
||||
delete formSchemaItem.hidden
|
||||
|
||||
formSchema.push(formSchemaItem)
|
||||
const formSchemaItem = {
|
||||
component: formItem?.form?.component || 'Input',
|
||||
...formItem.form,
|
||||
field: formItem.field,
|
||||
label: formItem.label
|
||||
}
|
||||
|
||||
formSchema.push(formSchemaItem)
|
||||
}
|
||||
|
||||
return formSchema
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
import introJs from 'intro.js'
|
||||
import { IntroJs, Step, Options } from 'intro.js'
|
||||
import 'intro.js/introjs.css'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
|
||||
export const useIntro = (setps?: Step[], options?: Options) => {
|
||||
const { t } = useI18n()
|
||||
|
||||
const { variables } = useDesign()
|
||||
|
||||
const defaultSetps: Step[] = setps || [
|
||||
{
|
||||
element: `#${variables.namespace}-menu`,
|
||||
title: t('common.menu'),
|
||||
intro: t('common.menuDes'),
|
||||
position: 'right'
|
||||
},
|
||||
{
|
||||
element: `#${variables.namespace}-tool-header`,
|
||||
title: t('common.tool'),
|
||||
intro: t('common.toolDes'),
|
||||
position: 'left'
|
||||
},
|
||||
{
|
||||
element: `#${variables.namespace}-tags-view`,
|
||||
title: t('common.tagsView'),
|
||||
intro: t('common.tagsViewDes'),
|
||||
position: 'bottom'
|
||||
}
|
||||
]
|
||||
|
||||
const defaultOptions: Options = options || {
|
||||
prevLabel: t('common.prevLabel'),
|
||||
nextLabel: t('common.nextLabel'),
|
||||
skipLabel: t('common.skipLabel'),
|
||||
doneLabel: t('common.doneLabel')
|
||||
}
|
||||
|
||||
const introRef: IntroJs = introJs()
|
||||
|
||||
introRef.addSteps(defaultSetps).setOptions(defaultOptions)
|
||||
|
||||
return {
|
||||
introRef
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||
|
||||
const appStore = useAppStoreWithOut()
|
||||
|
||||
export const usePageLoading = () => {
|
||||
const loadStart = () => {
|
||||
const appStore = useAppStoreWithOut()
|
||||
appStore.setPageLoading(true)
|
||||
}
|
||||
|
||||
const loadDone = () => {
|
||||
const appStore = useAppStoreWithOut()
|
||||
appStore.setPageLoading(false)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
import { useTagsViewStoreWithOut } from '@/store/modules/tagsView'
|
||||
import { RouteLocationNormalizedLoaded, useRouter } from 'vue-router'
|
||||
import { computed, nextTick, unref } from 'vue'
|
||||
|
||||
export const useTagsView = () => {
|
||||
const tagsViewStore = useTagsViewStoreWithOut()
|
||||
|
||||
const { replace, currentRoute } = useRouter()
|
||||
|
||||
const selectedTag = computed(() => tagsViewStore.getSelectedTag)
|
||||
|
||||
const closeAll = (callback?: Fn) => {
|
||||
tagsViewStore.delAllViews()
|
||||
callback?.()
|
||||
}
|
||||
|
||||
const closeLeft = (callback?: Fn) => {
|
||||
tagsViewStore.delLeftViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||
callback?.()
|
||||
}
|
||||
|
||||
const closeRight = (callback?: Fn) => {
|
||||
tagsViewStore.delRightViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||
callback?.()
|
||||
}
|
||||
|
||||
const closeOther = (callback?: Fn) => {
|
||||
tagsViewStore.delOthersViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||
callback?.()
|
||||
}
|
||||
|
||||
const closeCurrent = (view?: RouteLocationNormalizedLoaded, callback?: Fn) => {
|
||||
if (view?.meta?.affix) return
|
||||
tagsViewStore.delView(view || unref(currentRoute))
|
||||
|
||||
callback?.()
|
||||
}
|
||||
|
||||
const refreshPage = async (view?: RouteLocationNormalizedLoaded, callback?: Fn) => {
|
||||
tagsViewStore.delCachedView()
|
||||
const { path, query } = view || unref(currentRoute)
|
||||
await nextTick()
|
||||
replace({
|
||||
path: '/redirect' + path,
|
||||
query: query
|
||||
})
|
||||
callback?.()
|
||||
}
|
||||
|
||||
const setTitle = (title: string, path?: string) => {
|
||||
tagsViewStore.setTitle(title, path)
|
||||
}
|
||||
|
||||
return {
|
||||
closeAll,
|
||||
closeLeft,
|
||||
closeRight,
|
||||
closeOther,
|
||||
closeCurrent,
|
||||
refreshPage,
|
||||
setTitle
|
||||
}
|
||||
}
|
|
@ -3,10 +3,10 @@ import { isString } from '@/utils/is'
|
|||
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
const appStore = useAppStoreWithOut()
|
||||
|
||||
export const useTitle = (newTitle?: string) => {
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStoreWithOut()
|
||||
|
||||
const title = ref(
|
||||
newTitle ? `${appStore.getTitle} - ${t(newTitle as string)}` : appStore.getTitle
|
||||
)
|
||||
|
|
|
@ -71,8 +71,14 @@ export default defineComponent({
|
|||
|
||||
.@{prefix-cls} {
|
||||
background-color: var(--app-content-bg-color);
|
||||
:deep(.@{elNamespace}-scrollbar__view) {
|
||||
height: 100% !important;
|
||||
.@{prefix-cls}-content-scrollbar {
|
||||
& > :deep(.el-scrollbar__wrap) {
|
||||
& > .@{elNamespace}-scrollbar__view {
|
||||
display: flex;
|
||||
height: 100% !important;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -6,10 +6,6 @@ import { computed } from 'vue'
|
|||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const layout = computed(() => appStore.getLayout)
|
||||
|
||||
const fixedHeader = computed(() => appStore.getFixedHeader)
|
||||
|
||||
const footer = computed(() => appStore.getFooter)
|
||||
|
||||
const tagsViewStore = useTagsViewStore()
|
||||
|
@ -17,39 +13,12 @@ const tagsViewStore = useTagsViewStore()
|
|||
const getCaches = computed((): string[] => {
|
||||
return tagsViewStore.getCachedViews
|
||||
})
|
||||
|
||||
const tagsView = computed(() => appStore.getTagsView)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
:class="[
|
||||
'p-[var(--app-content-padding)] w-[calc(100%-var(--app-content-padding)-var(--app-content-padding))] bg-[var(--app-content-bg-color)] dark:bg-[var(--el-bg-color)]',
|
||||
{
|
||||
'!min-h-[calc(100%-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height))]':
|
||||
(fixedHeader &&
|
||||
(layout === 'classic' || layout === 'topLeft' || layout === 'top') &&
|
||||
footer) ||
|
||||
(!tagsView && layout === 'top' && footer),
|
||||
|
||||
'!min-h-[calc(100%-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height)-var(--tags-view-height))]':
|
||||
tagsView && layout === 'top' && footer,
|
||||
|
||||
'!min-h-[calc(100%-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-var(--top-tool-height)-var(--app-footer-height))]':
|
||||
!fixedHeader && layout === 'classic' && footer,
|
||||
|
||||
'!min-h-[calc(100%-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height))]':
|
||||
!fixedHeader && layout === 'topLeft' && footer,
|
||||
|
||||
// '!min-h-[calc(100%-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height)-var(--tags-view-height)-var(--top-tool-height))]':
|
||||
// !fixedHeader && layout === 'top' && footer,
|
||||
|
||||
'!min-h-[calc(100%-var(--top-tool-height)-var(--app-content-padding)-var(--app-content-padding))]':
|
||||
fixedHeader && layout === 'cutMenu' && footer,
|
||||
|
||||
'!min-h-[calc(100%-var(--top-tool-height)-var(--app-content-padding)-var(--app-content-padding)-var(--tags-view-height))]':
|
||||
!fixedHeader && layout === 'cutMenu' && footer
|
||||
}
|
||||
'flex-1 p-[var(--app-content-padding)] w-[calc(100%-var(--app-content-padding)-var(--app-content-padding))] bg-[var(--app-content-bg-color)] dark:bg-[var(--el-bg-color)]'
|
||||
]"
|
||||
>
|
||||
<router-view>
|
||||
|
|
|
@ -41,8 +41,7 @@ export default defineComponent({
|
|||
id={`${variables.namespace}-tool-header`}
|
||||
class={[
|
||||
prefixCls,
|
||||
'h-[var(--top-tool-height)] relative px-[var(--top-tool-p-x)] flex items-center justify-between',
|
||||
'dark:bg-[var(--el-bg-color)]'
|
||||
'h-[var(--top-tool-height)] relative px-[var(--top-tool-p-x)] flex items-center justify-between'
|
||||
]}
|
||||
>
|
||||
{layout.value !== 'top' ? (
|
||||
|
|
|
@ -91,7 +91,8 @@ export default {
|
|||
tagsViewIcon: 'Tags view icon',
|
||||
dynamicRouter: 'Dynamic router',
|
||||
reExperienced: 'Please exit the login experience again',
|
||||
fixedMenu: 'Fixed menu'
|
||||
fixedMenu: 'Fixed menu',
|
||||
serverDynamicRouter: 'Server dynamic router'
|
||||
},
|
||||
size: {
|
||||
default: 'Default',
|
||||
|
|
|
@ -91,7 +91,8 @@ export default {
|
|||
tagsViewIcon: '标签页图标',
|
||||
dynamicRouter: '动态路由',
|
||||
reExperienced: '请重新退出登录体验',
|
||||
fixedMenu: '固定菜单'
|
||||
fixedMenu: '固定菜单',
|
||||
serverDynamicRouter: '服务端动态路由'
|
||||
},
|
||||
size: {
|
||||
default: '默认',
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// 引入windi css
|
||||
import 'vue/jsx'
|
||||
|
||||
// 引入unocss
|
||||
import '@/plugins/unocss'
|
||||
|
||||
// 导入全局的svg图标
|
||||
|
@ -25,9 +27,6 @@ import '@/plugins/animate.css'
|
|||
// 路由
|
||||
import { setupRouter } from './router'
|
||||
|
||||
// 权限
|
||||
import { setupPermission } from './directives'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
|
||||
import App from './App.vue'
|
||||
|
@ -48,8 +47,6 @@ const setupAll = async () => {
|
|||
|
||||
setupRouter(app)
|
||||
|
||||
setupPermission(app)
|
||||
|
||||
app.mount('#app')
|
||||
}
|
||||
|
||||
|
|
|
@ -1,59 +1,42 @@
|
|||
import router from './router'
|
||||
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { useTitle } from '@/hooks/web/useTitle'
|
||||
import { useNProgress } from '@/hooks/web/useNProgress'
|
||||
import { usePermissionStoreWithOut } from '@/store/modules/permission'
|
||||
import { useDictStoreWithOut } from '@/store/modules/dict'
|
||||
import { usePageLoading } from '@/hooks/web/usePageLoading'
|
||||
import { getDictApi } from '@/api/common'
|
||||
|
||||
const permissionStore = usePermissionStoreWithOut()
|
||||
|
||||
const appStore = useAppStoreWithOut()
|
||||
|
||||
const dictStore = useDictStoreWithOut()
|
||||
|
||||
const { getStorage } = useStorage()
|
||||
import { NO_REDIRECT_WHITE_LIST } from '@/constants'
|
||||
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||
|
||||
const { start, done } = useNProgress()
|
||||
|
||||
const { loadStart, loadDone } = usePageLoading()
|
||||
|
||||
const whiteList = ['/login'] // 不重定向白名单
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
start()
|
||||
loadStart()
|
||||
if (getStorage(appStore.getUserInfo)) {
|
||||
const permissionStore = usePermissionStoreWithOut()
|
||||
const appStore = useAppStoreWithOut()
|
||||
const userStore = useUserStoreWithOut()
|
||||
if (userStore.getUserInfo) {
|
||||
if (to.path === '/login') {
|
||||
next({ path: '/' })
|
||||
} else {
|
||||
if (!dictStore.getIsSetDict) {
|
||||
// 获取所有字典
|
||||
const res = await getDictApi()
|
||||
if (res) {
|
||||
dictStore.setDictObj(res.data)
|
||||
dictStore.setIsSetDict(true)
|
||||
}
|
||||
}
|
||||
if (permissionStore.getIsAddRouters) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
// 开发者可根据实际情况进行修改
|
||||
const roleRouters = getStorage('roleRouters') || []
|
||||
const userInfo = getStorage(appStore.getUserInfo)
|
||||
const roleRouters = userStore.getRoleRouters || []
|
||||
|
||||
// 是否使用动态路由
|
||||
if (appStore.getDynamicRouter) {
|
||||
userInfo.role === 'admin'
|
||||
? await permissionStore.generateRoutes('admin', roleRouters as AppCustomRouteRecordRaw[])
|
||||
: await permissionStore.generateRoutes('test', roleRouters as string[])
|
||||
appStore.serverDynamicRouter
|
||||
? await permissionStore.generateRoutes('server', roleRouters as AppCustomRouteRecordRaw[])
|
||||
: await permissionStore.generateRoutes('frontEnd', roleRouters as string[])
|
||||
} else {
|
||||
await permissionStore.generateRoutes('none')
|
||||
await permissionStore.generateRoutes('static')
|
||||
}
|
||||
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
|
@ -66,7 +49,7 @@ router.beforeEach(async (to, from, next) => {
|
|||
next(nextData)
|
||||
}
|
||||
} else {
|
||||
if (whiteList.indexOf(to.path) !== -1) {
|
||||
if (NO_REDIRECT_WHITE_LIST.indexOf(to.path) !== -1) {
|
||||
next()
|
||||
} else {
|
||||
next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
|
||||
|
|
|
@ -12,7 +12,13 @@ export const setupElementPlus = (app: App<Element>) => {
|
|||
app.use(plugin)
|
||||
})
|
||||
|
||||
// 为了开发环境启动更快,一次性引入所有样式
|
||||
if (import.meta.env.VITE_USE_ALL_ELEMENT_PLUS_STYLE === 'true') {
|
||||
import('element-plus/dist/index.css')
|
||||
return
|
||||
}
|
||||
|
||||
components.forEach((component) => {
|
||||
app.component(component.name, component)
|
||||
app.component(component.name!, component)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
import 'virtual:svg-icons-register'
|
||||
|
||||
import '@purge-icons/generated'
|
||||
|
|
|
@ -10,7 +10,7 @@ export const constantRouterMap: AppRouteRecordRaw[] = [
|
|||
{
|
||||
path: '/',
|
||||
component: Layout,
|
||||
redirect: '/dashboard/analysis',
|
||||
redirect: '/level',
|
||||
name: 'Root',
|
||||
meta: {
|
||||
hidden: true
|
||||
|
@ -56,292 +56,6 @@ export const constantRouterMap: AppRouteRecordRaw[] = [
|
|||
]
|
||||
|
||||
export const asyncRouterMap: AppRouteRecordRaw[] = [
|
||||
{
|
||||
path: '/dashboard',
|
||||
component: Layout,
|
||||
redirect: '/dashboard/analysis',
|
||||
name: 'Dashboard',
|
||||
meta: {
|
||||
title: t('router.dashboard'),
|
||||
icon: 'ant-design:dashboard-filled',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'analysis',
|
||||
component: () => import('@/views/Dashboard/Analysis.vue'),
|
||||
name: 'Analysis',
|
||||
meta: {
|
||||
title: t('router.analysis'),
|
||||
noCache: true,
|
||||
affix: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'workplace',
|
||||
component: () => import('@/views/Dashboard/Workplace.vue'),
|
||||
name: 'Workplace',
|
||||
meta: {
|
||||
title: t('router.workplace'),
|
||||
noCache: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/external-link',
|
||||
component: Layout,
|
||||
meta: {},
|
||||
name: 'ExternalLink',
|
||||
children: [
|
||||
{
|
||||
path: 'https://element-plus-admin-doc.cn/',
|
||||
name: 'DocumentLink',
|
||||
meta: {
|
||||
title: t('router.document'),
|
||||
icon: 'clarity:document-solid'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/guide',
|
||||
component: Layout,
|
||||
name: 'Guide',
|
||||
meta: {},
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
component: () => import('@/views/Guide/Guide.vue'),
|
||||
name: 'GuideDemo',
|
||||
meta: {
|
||||
title: t('router.guide'),
|
||||
icon: 'cib:telegram-plane'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/components',
|
||||
component: Layout,
|
||||
name: 'ComponentsDemo',
|
||||
meta: {
|
||||
title: t('router.component'),
|
||||
icon: 'bx:bxs-component',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'form',
|
||||
component: getParentLayout(),
|
||||
redirect: '/components/form/default-form',
|
||||
name: 'Form',
|
||||
meta: {
|
||||
title: t('router.form'),
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'default-form',
|
||||
component: () => import('@/views/Components/Form/DefaultForm.vue'),
|
||||
name: 'DefaultForm',
|
||||
meta: {
|
||||
title: t('router.defaultForm')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'use-form',
|
||||
component: () => import('@/views/Components/Form/UseFormDemo.vue'),
|
||||
name: 'UseForm',
|
||||
meta: {
|
||||
title: 'UseForm'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'table',
|
||||
component: getParentLayout(),
|
||||
redirect: '/components/table/default-table',
|
||||
name: 'TableDemo',
|
||||
meta: {
|
||||
title: t('router.table'),
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'default-table',
|
||||
component: () => import('@/views/Components/Table/DefaultTable.vue'),
|
||||
name: 'DefaultTable',
|
||||
meta: {
|
||||
title: t('router.defaultTable')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'use-table',
|
||||
component: () => import('@/views/Components/Table/UseTableDemo.vue'),
|
||||
name: 'UseTable',
|
||||
meta: {
|
||||
title: 'UseTable'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'tree-table',
|
||||
component: () => import('@/views/Components/Table/TreeTable.vue'),
|
||||
name: 'TreeTable',
|
||||
meta: {
|
||||
title: t('router.treeTable')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'table-image-preview',
|
||||
component: () => import('@/views/Components/Table/TableImagePreview.vue'),
|
||||
name: 'TableImagePreview',
|
||||
meta: {
|
||||
title: t('router.PicturePreview')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'editor-demo',
|
||||
component: getParentLayout(),
|
||||
redirect: '/components/editor-demo/editor',
|
||||
name: 'EditorDemo',
|
||||
meta: {
|
||||
title: t('router.editor'),
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'editor',
|
||||
component: () => import('@/views/Components/Editor/Editor.vue'),
|
||||
name: 'Editor',
|
||||
meta: {
|
||||
title: t('router.richText')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'search',
|
||||
component: () => import('@/views/Components/Search.vue'),
|
||||
name: 'Search',
|
||||
meta: {
|
||||
title: t('router.search')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'descriptions',
|
||||
component: () => import('@/views/Components/Descriptions.vue'),
|
||||
name: 'Descriptions',
|
||||
meta: {
|
||||
title: t('router.descriptions')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'image-viewer',
|
||||
component: () => import('@/views/Components/ImageViewer.vue'),
|
||||
name: 'ImageViewer',
|
||||
meta: {
|
||||
title: t('router.imageViewer')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'dialog',
|
||||
component: () => import('@/views/Components/Dialog.vue'),
|
||||
name: 'Dialog',
|
||||
meta: {
|
||||
title: t('router.dialog')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'icon',
|
||||
component: () => import('@/views/Components/Icon.vue'),
|
||||
name: 'Icon',
|
||||
meta: {
|
||||
title: t('router.icon')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'echart',
|
||||
component: () => import('@/views/Components/Echart.vue'),
|
||||
name: 'Echart',
|
||||
meta: {
|
||||
title: t('router.echart')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'count-to',
|
||||
component: () => import('@/views/Components/CountTo.vue'),
|
||||
name: 'CountTo',
|
||||
meta: {
|
||||
title: t('router.countTo')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'qrcode',
|
||||
component: () => import('@/views/Components/Qrcode.vue'),
|
||||
name: 'Qrcode',
|
||||
meta: {
|
||||
title: t('router.qrcode')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'highlight',
|
||||
component: () => import('@/views/Components/Highlight.vue'),
|
||||
name: 'Highlight',
|
||||
meta: {
|
||||
title: t('router.highlight')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'infotip',
|
||||
component: () => import('@/views/Components/Infotip.vue'),
|
||||
name: 'Infotip',
|
||||
meta: {
|
||||
title: t('router.infotip')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'input-password',
|
||||
component: () => import('@/views/Components/InputPassword.vue'),
|
||||
name: 'InputPassword',
|
||||
meta: {
|
||||
title: t('router.inputPassword')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/hooks',
|
||||
component: Layout,
|
||||
redirect: '/hooks/useWatermark',
|
||||
name: 'Hooks',
|
||||
meta: {
|
||||
title: 'hooks',
|
||||
icon: 'ic:outline-webhook',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'useWatermark',
|
||||
component: () => import('@/views/hooks/useWatermark.vue'),
|
||||
name: 'UseWatermark',
|
||||
meta: {
|
||||
title: 'useWatermark'
|
||||
}
|
||||
}
|
||||
// {
|
||||
// path: 'useCrudSchemas',
|
||||
// component: () => import('@/views/hooks/useCrudSchemas.vue'),
|
||||
// name: 'UseCrudSchemas',
|
||||
// meta: {
|
||||
// title: 'useCrudSchemas'
|
||||
// }
|
||||
// }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/level',
|
||||
component: Layout,
|
||||
|
@ -400,156 +114,6 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/example',
|
||||
component: Layout,
|
||||
redirect: '/example/example-dialog',
|
||||
name: 'Example',
|
||||
meta: {
|
||||
title: t('router.example'),
|
||||
icon: 'ep:management',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'example-dialog',
|
||||
component: () => import('@/views/Example/Dialog/ExampleDialog.vue'),
|
||||
name: 'ExampleDialog',
|
||||
meta: {
|
||||
title: t('router.exampleDialog')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-page',
|
||||
component: () => import('@/views/Example/Page/ExamplePage.vue'),
|
||||
name: 'ExamplePage',
|
||||
meta: {
|
||||
title: t('router.examplePage')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-add',
|
||||
component: () => import('@/views/Example/Page/ExampleAdd.vue'),
|
||||
name: 'ExampleAdd',
|
||||
meta: {
|
||||
title: t('router.exampleAdd'),
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
canTo: true,
|
||||
activeMenu: '/example/example-page'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-edit',
|
||||
component: () => import('@/views/Example/Page/ExampleEdit.vue'),
|
||||
name: 'ExampleEdit',
|
||||
meta: {
|
||||
title: t('router.exampleEdit'),
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
canTo: true,
|
||||
activeMenu: '/example/example-page'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-detail',
|
||||
component: () => import('@/views/Example/Page/ExampleDetail.vue'),
|
||||
name: 'ExampleDetail',
|
||||
meta: {
|
||||
title: t('router.exampleDetail'),
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
canTo: true,
|
||||
activeMenu: '/example/example-page'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/error',
|
||||
component: Layout,
|
||||
redirect: '/error/404',
|
||||
name: 'Error',
|
||||
meta: {
|
||||
title: t('router.errorPage'),
|
||||
icon: 'ci:error',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '404-demo',
|
||||
component: () => import('@/views/Error/404.vue'),
|
||||
name: '404Demo',
|
||||
meta: {
|
||||
title: '404'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '403-demo',
|
||||
component: () => import('@/views/Error/403.vue'),
|
||||
name: '403Demo',
|
||||
meta: {
|
||||
title: '403'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '500-demo',
|
||||
component: () => import('@/views/Error/500.vue'),
|
||||
name: '500Demo',
|
||||
meta: {
|
||||
title: '500'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/authorization',
|
||||
component: Layout,
|
||||
redirect: '/authorization/user',
|
||||
name: 'Authorization',
|
||||
meta: {
|
||||
title: t('router.authorization'),
|
||||
icon: 'eos-icons:role-binding',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'department',
|
||||
component: () => import('@/views/Authorization/Department/Department.vue'),
|
||||
name: 'Department',
|
||||
meta: {
|
||||
title: t('router.department')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'user',
|
||||
component: () => import('@/views/Authorization/User/User.vue'),
|
||||
name: 'User',
|
||||
meta: {
|
||||
title: t('router.user')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'menu',
|
||||
component: () => import('@/views/Authorization/Menu/Menu.vue'),
|
||||
name: 'Menu',
|
||||
meta: {
|
||||
title: t('router.menuManagement')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'role',
|
||||
component: () => import('@/views/Authorization/Role/Role.vue'),
|
||||
name: 'Role',
|
||||
meta: {
|
||||
title: t('router.role')
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import type { App } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import piniaPersist from 'pinia-plugin-persist'
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
|
||||
const store = createPinia()
|
||||
|
||||
store.use(piniaPersist)
|
||||
store.use(piniaPluginPersistedstate)
|
||||
|
||||
export const setupStore = (app: App<Element>) => {
|
||||
app.use(store)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue