mirror of
https://github.com/fantastic-admin/basic.git
synced 2026-10-07 06:33:18 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffd30c62b7 | ||
|
|
86d0fc6d22 | ||
|
|
b7d212352e | ||
|
|
24e922077f | ||
|
|
d502b58649 | ||
|
|
eb64300242 | ||
|
|
1b7667ffc8 | ||
|
|
60cc6691cc | ||
|
|
94ce1ac6f9 | ||
|
|
4172877fa5 | ||
|
|
d736a08750 | ||
|
|
1a8529fd99 | ||
|
|
0f347b6d72 | ||
|
|
12d044ab99 | ||
|
|
20c7c51527 | ||
|
|
3dc4f750ad | ||
|
|
b3c12c9f37 | ||
|
|
e6e1ec9d12 | ||
|
|
98d288f69f | ||
|
|
b297d38aa4 | ||
|
|
606f6c3e14 | ||
|
|
6632ffbc65 | ||
|
|
dc625c3c26 | ||
|
|
84e0630dd0 | ||
|
|
d4c465734f | ||
|
|
ba8683a66e | ||
|
|
bcbe576000 | ||
|
|
198dd88e3e | ||
|
|
417508426f | ||
|
|
40aa56c064 | ||
|
|
e18c4b257a | ||
|
|
e1ac42629b | ||
|
|
303aa75fd6 | ||
|
|
a2d5294285 | ||
|
|
58182122dd | ||
|
|
fb05790479 | ||
|
|
c90138c718 | ||
|
|
54c3756a61 | ||
|
|
d6d7dadab7 | ||
|
|
fab8f2cfdb | ||
|
|
11e68df11b | ||
|
|
2109c194d6 | ||
|
|
247b5aa18b |
@@ -18,6 +18,6 @@ jobs:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }}
|
||||
with:
|
||||
# 注意替换为你的 GitHub 源仓库地址
|
||||
source-repo: git@github.com:hooray/fantastic-admin.git
|
||||
source-repo: git@github.com:fantastic-admin/basic.git
|
||||
# 注意替换为你的 Gitee 目标仓库地址
|
||||
destination-repo: git@gitee.com:hooray/fantastic-admin.git
|
||||
destination-repo: git@gitee.com:fantastic-admin/basic.git
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"extends": [
|
||||
"stylelint-stylistic/config",
|
||||
"stylelint-config-standard",
|
||||
"stylelint-config-standard-scss",
|
||||
"stylelint-config-standard-vue/scss"
|
||||
],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<p align="center">
|
||||
<img src="https://hooray.gitee.io/fantastic-admin/logo.png" width="200" height="200" />
|
||||
<img src="https://fantastic-admin.gitee.io/logo.png" width="200" height="200" />
|
||||
</p>
|
||||
|
||||
<h1 align="center">Fantastic-admin</h1>
|
||||
@@ -7,21 +7,21 @@
|
||||
<p align="center">一款<b>开箱即用</b>的 Vue 中后台管理系统框架</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://hooray.gitee.io/fantastic-admin" target="_blank">官网</a>
|
||||
( <a href="https://hooray.github.io/fantastic-admin" target="_blank">备用</a> )
|
||||
<a href="https://fantastic-admin.gitee.io" target="_blank">官网</a>
|
||||
( <a href="https://fantastic-admin.github.io" target="_blank">备用</a> )
|
||||
<span> | </span>
|
||||
演示
|
||||
( <a href="https://hooray.gitee.io/fantastic-admin-example" target="_blank">基础版</a>
|
||||
<a href="https://hooray.gitee.io/fantastic-admin-pro-example" target="_blank">专业版</a> )
|
||||
( <a href="https://fantastic-admin.gitee.io/basic-example" target="_blank">基础版</a>
|
||||
<a href="https://fantastic-admin.gitee.io/pro-example" target="_blank">专业版</a> )
|
||||
<span> | </span>
|
||||
<a href="https://hooray.gitee.io/fantastic-admin/support.html" target="_blank">技术支持</a>
|
||||
<a href="https://fantastic-admin.gitee.io/support.html" target="_blank">技术支持</a>
|
||||
<span> | </span>
|
||||
<a href="https://hooray.gitee.io/fantastic-admin/buy.html" target="_blank">购买</a>
|
||||
<a href="https://fantastic-admin.gitee.io/buy.html" target="_blank">购买</a>
|
||||
<p>
|
||||
|
||||
<p align="center">
|
||||
<a href="###"><img src="https://img.shields.io/github/license/hooray/fantastic-admin?label=%E5%BC%80%E6%BA%90%E5%8D%8F%E8%AE%AE&style=flat-square" /></a>
|
||||
<a href="https://github.com/hooray/fantastic-admin/releases" target="_blank"><img src="https://img.shields.io/github/v/release/hooray/fantastic-admin?label=%E5%BD%93%E5%89%8D%E7%89%88%E6%9C%AC&style=flat-square" /></a>
|
||||
<a href="###"><img src="https://img.shields.io/github/license/fantastic-admin/basic?label=%E5%BC%80%E6%BA%90%E5%8D%8F%E8%AE%AE&style=flat-square" /></a>
|
||||
<a href="https://github.com/fantastic-admin/basic/releases" target="_blank"><img src="https://img.shields.io/github/v/release/fantastic-admin/basic?label=%E5%BD%93%E5%89%8D%E7%89%88%E6%9C%AC&style=flat-square" /></a>
|
||||
</p>
|
||||
|
||||
## 特点
|
||||
@@ -38,12 +38,12 @@
|
||||
|
||||
## 分支说明
|
||||
|
||||
- `master` Vue3 版本框架源码分支,不含示例代码,可直接用于实际开发
|
||||
- `main` Vue3 版本框架源码分支,不含示例代码,可直接用于实际开发
|
||||
- `example` Vue3 版本演示源码分支,同线上演示站,包含大量示例,可用于参考学习
|
||||
- `vue2` Vue2 版本框架源码分支,不含示例代码,可直接用于实际开发(停止维护)
|
||||
- `vue2-example` Vue2 版本演示源码分支,包含大量示例,可用于参考学习(停止维护)
|
||||
|
||||
如果你更注重使用时的稳定性,推荐去 [Github Releases](https://github.com/hooray/fantastic-admin/releases) 页面下载最新稳定版本的压缩包。
|
||||
如果你更注重使用时的稳定性,推荐去 [Github Releases](https://github.com/fantastic-admin/basic/releases) 页面下载最新稳定版本的压缩包。
|
||||
|
||||
## 预览
|
||||
|
||||
@@ -51,14 +51,14 @@
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="https://hooray.gitee.io/fantastic-admin/preview1.png" /></td>
|
||||
<td><img src="https://hooray.gitee.io/fantastic-admin/preview2.png" /></td>
|
||||
<td><img src="https://hooray.gitee.io/fantastic-admin/preview3.png" /></td>
|
||||
<td><img src="https://fantastic-admin.gitee.io/preview1.png" /></td>
|
||||
<td><img src="https://fantastic-admin.gitee.io/preview2.png" /></td>
|
||||
<td><img src="https://fantastic-admin.gitee.io/preview3.png" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://hooray.gitee.io/fantastic-admin/preview4.png" /></td>
|
||||
<td><img src="https://hooray.gitee.io/fantastic-admin/preview5.png" /></td>
|
||||
<td><img src="https://hooray.gitee.io/fantastic-admin/preview6.png" /></td>
|
||||
<td><img src="https://fantastic-admin.gitee.io/preview4.png" /></td>
|
||||
<td><img src="https://fantastic-admin.gitee.io/preview5.png" /></td>
|
||||
<td><img src="https://fantastic-admin.gitee.io/preview6.png" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -66,9 +66,9 @@
|
||||
|
||||
如果觉得 Fantastic-admin 这个框架不错,或者已经在使用了,希望你可以在 **Github** 或者 **Gitee(码云)** 帮我点个 ⭐ ,这将对我是极大的鼓励。
|
||||
|
||||
[](https://github.com/hooray/fantastic-admin/stargazers)
|
||||
[](https://github.com/fantastic-admin/basic)
|
||||
|
||||
[](https://gitee.com/hooray/fantastic-admin/stargazers)
|
||||
[](https://gitee.com/fantastic-admin/basic)
|
||||
|
||||
## 生态
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
<table>
|
||||
<tr>
|
||||
<th colspan="3" align="center">
|
||||
<a href="https://hooray.gitee.io/one-step-admin/" target="_blank">One-step-admin</a>
|
||||
<a href="https://one-step-admin.gitee.io" target="_blank">One-step-admin</a>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -97,13 +97,13 @@
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://hooray.gitee.io/one-step-admin/preview1.png" /></td>
|
||||
<td><img src="https://hooray.gitee.io/one-step-admin/preview2.png" /></td>
|
||||
<td><img src="https://hooray.gitee.io/one-step-admin/preview3.png" /></td>
|
||||
<td><img src="https://one-step-admin.gitee.io/preview1.png" /></td>
|
||||
<td><img src="https://one-step-admin.gitee.io/preview2.png" /></td>
|
||||
<td><img src="https://one-step-admin.gitee.io/preview3.png" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://hooray.gitee.io/one-step-admin/preview4.png" /></td>
|
||||
<td><img src="https://hooray.gitee.io/one-step-admin/preview5.png" /></td>
|
||||
<td><img src="https://hooray.gitee.io/one-step-admin/preview6.png" /></td>
|
||||
<td><img src="https://one-step-admin.gitee.io/preview4.png" /></td>
|
||||
<td><img src="https://one-step-admin.gitee.io/preview5.png" /></td>
|
||||
<td><img src="https://one-step-admin.gitee.io/preview6.png" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
<link rel="stylesheet" href="/loading.css" />
|
||||
<link rel="stylesheet" href="/browser_upgrade/index.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Cache-control" content="no-cache">
|
||||
<meta http-equiv="Cache" content="no-cache">
|
||||
<title>%VITE_APP_TITLE%</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
+38
-39
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "2.11.0",
|
||||
"version": "3.2.1",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build:test": "vue-tsc --noEmit && vite build --mode test",
|
||||
@@ -18,69 +18,68 @@
|
||||
"release": "bumpp"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^9.13.0",
|
||||
"@vueuse/integrations": "^9.13.0",
|
||||
"axios": "^1.3.5",
|
||||
"dayjs": "^1.11.7",
|
||||
"element-plus": "^2.3.3",
|
||||
"eruda": "^3.0.0",
|
||||
"hotkeys-js": "^3.10.2",
|
||||
"@vueuse/core": "^10.2.1",
|
||||
"@vueuse/integrations": "^10.2.1",
|
||||
"axios": "^1.4.0",
|
||||
"dayjs": "^1.11.9",
|
||||
"element-plus": "^2.3.8",
|
||||
"eruda": "^3.0.1",
|
||||
"hotkeys-js": "^3.11.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mitt": "^3.0.0",
|
||||
"mitt": "^3.0.1",
|
||||
"mockjs": "^1.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"path-to-regexp": "^6.2.1",
|
||||
"pinia": "^2.0.34",
|
||||
"qs": "^6.11.1",
|
||||
"vconsole": "^3.15.0",
|
||||
"vue": "^3.2.47",
|
||||
"vue-router": "^4.1.6"
|
||||
"pinia": "^2.1.6",
|
||||
"qs": "^6.11.2",
|
||||
"vconsole": "^3.15.1",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^0.38.4",
|
||||
"@iconify-json/ep": "^1.1.10",
|
||||
"@antfu/eslint-config": "^0.39.8",
|
||||
"@iconify-json/ep": "^1.1.11",
|
||||
"@iconify/vue": "^4.1.1",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/path-browserify": "^1.0.0",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@vitejs/plugin-vue": "^4.1.0",
|
||||
"@vitejs/plugin-legacy": "^4.1.1",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"bumpp": "^9.1.0",
|
||||
"cz-git": "^1.6.1",
|
||||
"eslint": "^8.38.0",
|
||||
"esno": "^0.16.3",
|
||||
"bumpp": "^9.1.1",
|
||||
"cz-git": "^1.7.0",
|
||||
"eslint": "^8.46.0",
|
||||
"esno": "^0.17.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"http-server": "^14.1.1",
|
||||
"lint-staged": "^13.2.1",
|
||||
"lint-staged": "^13.2.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"plop": "^3.1.2",
|
||||
"postcss-html": "^1.5.0",
|
||||
"sass": "^1.62.0",
|
||||
"simple-git-hooks": "^2.8.1",
|
||||
"stylelint": "^15.4.0",
|
||||
"stylelint-config-standard": "^32.0.0",
|
||||
"stylelint-config-standard-scss": "^8.0.0",
|
||||
"sass": "^1.64.1",
|
||||
"simple-git-hooks": "^2.9.0",
|
||||
"stylelint": "^15.10.2",
|
||||
"stylelint-config-standard-scss": "^10.0.0",
|
||||
"stylelint-config-standard-vue": "^1.0.0",
|
||||
"stylelint-scss": "^4.6.0",
|
||||
"stylelint-stylistic": "^0.4.0",
|
||||
"stylelint-scss": "^5.0.1",
|
||||
"stylelint-stylistic": "^0.4.3",
|
||||
"svgo": "^3.0.2",
|
||||
"terser": "^5.16.9",
|
||||
"typescript": "^5.0.4",
|
||||
"unplugin-auto-import": "^0.15.2",
|
||||
"unplugin-vue-components": "^0.24.1",
|
||||
"unplugin-vue-setup-extend-plus": "^1.0.0",
|
||||
"vite": "^4.2.1",
|
||||
"terser": "^5.19.2",
|
||||
"typescript": "^5.1.6",
|
||||
"unplugin-auto-import": "^0.16.6",
|
||||
"unplugin-vue-components": "^0.25.1",
|
||||
"vite": "^4.4.7",
|
||||
"vite-plugin-banner": "^0.7.0",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-mock": "^2.9.6",
|
||||
"vite-plugin-pages": "^0.29.0",
|
||||
"vite-plugin-mock": "^2.9.8",
|
||||
"vite-plugin-pages": "^0.31.0",
|
||||
"vite-plugin-spritesmith": "^0.1.1",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vite-plugin-vue-inspector": "^3.4.0",
|
||||
"vite-plugin-vue-inspector": "^3.5.0",
|
||||
"vite-plugin-vue-meta-layouts": "^0.2.2",
|
||||
"vue-tsc": "^1.2.0"
|
||||
"vue-tsc": "^1.8.8"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
"pre-commit": "pnpm lint-staged",
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<script lang="ts" setup{{#if isGlobal}} name="{{ properCase name }}"{{/if}}>
|
||||
// 逻辑代码
|
||||
<script setup lang="ts">
|
||||
{{#if isGlobal}}
|
||||
defineOptions({
|
||||
name: '{{ properCase name }}',
|
||||
})
|
||||
{{/if}}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const fs = require('fs')
|
||||
const fs = require('node:fs')
|
||||
|
||||
function getFolder(path) {
|
||||
const components = []
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
meta:
|
||||
title: 页面标题
|
||||
</route>
|
||||
{{/if}}
|
||||
|
||||
<script lang="ts" setup name="{{ properCase componentName }}">
|
||||
// 逻辑代码
|
||||
{{/if}}
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: '{{ properCase componentName }}',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const path = require('node:path')
|
||||
const fs = require('node:fs')
|
||||
|
||||
function getFolder(path) {
|
||||
const components = []
|
||||
|
||||
+5
-3
@@ -1,6 +1,8 @@
|
||||
const fs = require('node:fs')
|
||||
|
||||
module.exports = function (plop) {
|
||||
plop.setWelcomeMessage('请选择需要创建的模式:')
|
||||
plop.setGenerator('page', require('./plop-templates/page/prompt'))
|
||||
plop.setGenerator('component', require('./plop-templates/component/prompt'))
|
||||
plop.setGenerator('store', require('./plop-templates/store/prompt'))
|
||||
fs.readdirSync('./plop-templates').forEach((item) => {
|
||||
fs.lstatSync(`./plop-templates/${item}`).isDirectory() && plop.setGenerator(item, require(`./plop-templates/${item}/prompt`))
|
||||
})
|
||||
}
|
||||
|
||||
Generated
+2978
-1379
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -86,7 +86,7 @@
|
||||
}
|
||||
|
||||
.fantastic-admin-home .text::after {
|
||||
content: '…';
|
||||
content: "…";
|
||||
position: absolute;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
<script lang="ts" setup>
|
||||
<script setup lang="ts">
|
||||
import eruda from 'eruda'
|
||||
import VConsole from 'vconsole'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
@@ -15,7 +15,7 @@ const buttonConfig = ref({
|
||||
|
||||
// 侧边栏主导航当前实际宽度
|
||||
const mainSidebarActualWidth = computed(() => {
|
||||
let actualWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--g-main-sidebar-width'))
|
||||
let actualWidth = Number.parseInt(getComputedStyle(document.documentElement).getPropertyValue('--g-main-sidebar-width'))
|
||||
if (['head', 'single'].includes(settingsStore.settings.menu.menuMode)) {
|
||||
actualWidth = 0
|
||||
}
|
||||
@@ -24,7 +24,7 @@ const mainSidebarActualWidth = computed(() => {
|
||||
|
||||
// 侧边栏次导航当前实际宽度
|
||||
const subSidebarActualWidth = computed(() => {
|
||||
let actualWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--g-sub-sidebar-width'))
|
||||
let actualWidth = Number.parseInt(getComputedStyle(document.documentElement).getPropertyValue('--g-sub-sidebar-width'))
|
||||
if (settingsStore.settings.menu.subMenuCollapse) {
|
||||
actualWidth = 64
|
||||
}
|
||||
|
||||
+2
-13
@@ -1,20 +1,9 @@
|
||||
import axios from 'axios'
|
||||
|
||||
// import qs from 'qs'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import router from '@/router/index'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
|
||||
function toLogin() {
|
||||
useUserStore().logout().then(() => {
|
||||
router.push({
|
||||
path: '/login',
|
||||
query: {
|
||||
redirect: router.currentRoute.value.path !== '/login' ? router.currentRoute.value.fullPath : undefined,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: (import.meta.env.DEV && import.meta.env.VITE_OPEN_PROXY === 'true') ? '/proxy/' : import.meta.env.VITE_APP_API_BASEURL,
|
||||
timeout: 1000 * 60,
|
||||
@@ -57,7 +46,7 @@ api.interceptors.response.use(
|
||||
}
|
||||
}
|
||||
else {
|
||||
toLogin()
|
||||
useUserStore().logout()
|
||||
}
|
||||
return Promise.resolve(response.data)
|
||||
},
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import api from '../index'
|
||||
|
||||
export default {
|
||||
// 后端获取路由数据
|
||||
routeList: () => api.get('app/route/list', {
|
||||
baseURL: '/mock/',
|
||||
}),
|
||||
|
||||
// 基于文件系统路由模式下,后端获取导航菜单数据
|
||||
menuList: () => api.get('app/menu/list', {
|
||||
baseURL: '/mock/',
|
||||
}),
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import api from '../index'
|
||||
|
||||
export default {
|
||||
// 登录
|
||||
login: (data: {
|
||||
account: string
|
||||
password: string
|
||||
}) => api.post('user/login', data, {
|
||||
baseURL: '/mock/',
|
||||
}),
|
||||
|
||||
// 获取权限
|
||||
permission: () => api.get('user/permission', {
|
||||
baseURL: '/mock/',
|
||||
}),
|
||||
|
||||
// 修改密码
|
||||
passwordEdit: (data: {
|
||||
password: string
|
||||
newpassword: string
|
||||
}) => api.post('user/password/edit', data, {
|
||||
baseURL: '/mock/',
|
||||
}),
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
<script lang="ts" setup name="Auth">
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'Auth',
|
||||
})
|
||||
|
||||
const props = defineProps<{
|
||||
value: string | string[]
|
||||
}>()
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<script lang="ts" setup name="AuthAll">
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'AuthAll',
|
||||
})
|
||||
|
||||
const props = defineProps<{
|
||||
value: string[]
|
||||
}>()
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
<script lang="ts" setup name="BatchActionBar">
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
selectionData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['checkAll', 'checkNull'])
|
||||
|
||||
const checkAll = computed({
|
||||
get() {
|
||||
let flag = false
|
||||
if (props.data.length !== 0 && props.data.length === props.selectionData.length) {
|
||||
flag = true
|
||||
}
|
||||
return flag
|
||||
},
|
||||
set(newVal) {
|
||||
newVal ? emit('checkAll') : emit('checkNull')
|
||||
},
|
||||
})
|
||||
|
||||
const isIndeterminate = computed(() => {
|
||||
let flag = false
|
||||
if (props.selectionData.length > 0 && props.selectionData.length < props.data.length) {
|
||||
flag = true
|
||||
}
|
||||
return flag
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="batch-action-bar">
|
||||
<el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" :disabled="!data.length">
|
||||
当页全选
|
||||
</el-checkbox>
|
||||
<div v-if="selectionData.length" class="tips">
|
||||
已选 {{ selectionData.length }} 项
|
||||
</div>
|
||||
<el-form :disabled="!selectionData.length">
|
||||
<slot />
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.batch-action-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 20px 0;
|
||||
padding: 0 10px;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.el-checkbox {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.tips {
|
||||
margin-right: 20px;
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.el-form) {
|
||||
> .el-button {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
> .el-button-group {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,51 +1,44 @@
|
||||
<script lang="ts" setup name="FileUpload">
|
||||
import { ElMessage } from 'element-plus'
|
||||
<script setup lang="ts">
|
||||
import type { UploadProps, UploadUserFile } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
headers: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: 'file',
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 2,
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
default: 3,
|
||||
},
|
||||
files: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
notip: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
ext: {
|
||||
type: Array,
|
||||
default: () => ['zip', 'rar'],
|
||||
},
|
||||
defineOptions({
|
||||
name: 'FileUpload',
|
||||
})
|
||||
|
||||
const emit = defineEmits(['onSuccess'])
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
action: UploadProps['action']
|
||||
headers?: UploadProps['headers']
|
||||
data?: UploadProps['data']
|
||||
name?: UploadProps['name']
|
||||
size?: number
|
||||
max?: number
|
||||
files?: UploadUserFile[]
|
||||
notip?: boolean
|
||||
ext?: string[]
|
||||
}>(),
|
||||
{
|
||||
name: 'file',
|
||||
size: 2,
|
||||
max: 3,
|
||||
files: () => [],
|
||||
notip: false,
|
||||
ext: () => ['zip', 'rar'],
|
||||
},
|
||||
)
|
||||
|
||||
const emits = defineEmits<{
|
||||
'onSuccess': [
|
||||
res: any,
|
||||
file: UploadUserFile,
|
||||
fileList: UploadUserFile[],
|
||||
]
|
||||
}>()
|
||||
|
||||
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
|
||||
const fileName = file.name.split('.')
|
||||
const fileExt = fileName.at(-1)
|
||||
const fileExt = fileName.at(-1) ?? ''
|
||||
const isTypeOk = props.ext.includes(fileExt)
|
||||
const isSizeOk = file.size / 1024 / 1024 < props.size
|
||||
if (!isTypeOk) {
|
||||
@@ -62,7 +55,7 @@ const onExceed: UploadProps['onExceed'] = () => {
|
||||
}
|
||||
|
||||
const onSuccess: UploadProps['onSuccess'] = (res, file, fileList) => {
|
||||
emit('onSuccess', res, file, fileList)
|
||||
emits('onSuccess', res, file, fileList)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -80,9 +73,7 @@ const onSuccess: UploadProps['onSuccess'] = (res, file, fileList) => {
|
||||
drag
|
||||
>
|
||||
<div class="slot">
|
||||
<el-icon class="el-icon--upload">
|
||||
<svg-icon name="ep:upload-filled" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:upload-filled" class="el-icon--upload" />
|
||||
<div class="el-upload__text">
|
||||
将文件拖到此处,或<em>点击上传</em>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<script lang="ts" setup name="FixedActionBar">
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'FixedActionBar',
|
||||
})
|
||||
|
||||
const isBottom = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
<script lang="ts" setup name="ImagePreview">
|
||||
const props = defineProps({
|
||||
src: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: '',
|
||||
},
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: '',
|
||||
},
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'ImagePreview',
|
||||
})
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
src: string
|
||||
width?: number | string
|
||||
height?: number | string
|
||||
}>(),
|
||||
{
|
||||
width: 200,
|
||||
height: 200,
|
||||
},
|
||||
)
|
||||
|
||||
const realWidth = computed(() => {
|
||||
return typeof props.width === 'string' ? props.width : `${props.width}px`
|
||||
})
|
||||
@@ -27,9 +28,7 @@ const realHeight = computed(() => {
|
||||
<el-image :src="src" fit="cover" :style="`width:${realWidth};height:${realHeight};`" :preview-src-list="[src]" preview-teleported>
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<el-icon>
|
||||
<svg-icon name="image-load-fail" />
|
||||
</el-icon>
|
||||
<svg-icon name="image-load-fail" />
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
|
||||
@@ -1,55 +1,45 @@
|
||||
<script lang="ts" setup name="ImageUpload">
|
||||
<script setup lang="ts">
|
||||
import type { UploadProps } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
headers: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: 'file',
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 2,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 150,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 150,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
notip: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
ext: {
|
||||
type: Array,
|
||||
default: () => ['jpg', 'png', 'gif', 'bmp'],
|
||||
},
|
||||
defineOptions({
|
||||
name: 'ImageUpload',
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:url', 'onSuccess'])
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
action: UploadProps['action']
|
||||
headers?: UploadProps['headers']
|
||||
data?: UploadProps['data']
|
||||
name?: UploadProps['name']
|
||||
url?: string
|
||||
size?: number
|
||||
width?: number
|
||||
height?: number
|
||||
placeholder?: string
|
||||
notip?: boolean
|
||||
ext?: string[]
|
||||
}>(),
|
||||
{
|
||||
name: 'file',
|
||||
url: '',
|
||||
size: 2,
|
||||
width: 150,
|
||||
height: 150,
|
||||
placeholder: '',
|
||||
notip: false,
|
||||
ext: () => ['jpg', 'png', 'gif', 'bmp'],
|
||||
},
|
||||
)
|
||||
|
||||
const emits = defineEmits<{
|
||||
'update:url': [
|
||||
url: string,
|
||||
]
|
||||
'onSuccess': [
|
||||
res: any,
|
||||
]
|
||||
}>()
|
||||
|
||||
const uploadData = ref({
|
||||
imageViewerVisible: false,
|
||||
@@ -69,11 +59,11 @@ function previewClose() {
|
||||
}
|
||||
// 移除
|
||||
function remove() {
|
||||
emit('update:url', '')
|
||||
emits('update:url', '')
|
||||
}
|
||||
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
|
||||
const fileName = file.name.split('.')
|
||||
const fileExt = fileName.at(-1)
|
||||
const fileExt = fileName.at(-1) ?? ''
|
||||
const isTypeOk = props.ext.includes(fileExt)
|
||||
const isSizeOk = file.size / 1024 / 1024 < props.size
|
||||
if (!isTypeOk) {
|
||||
@@ -93,7 +83,7 @@ const onProgress: UploadProps['onProgress'] = (file) => {
|
||||
const onSuccess: UploadProps['onSuccess'] = (res) => {
|
||||
uploadData.value.progress.preview = ''
|
||||
uploadData.value.progress.percent = 0
|
||||
emit('onSuccess', res)
|
||||
emits('onSuccess', res)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -114,9 +104,7 @@ const onSuccess: UploadProps['onSuccess'] = (res) => {
|
||||
<el-image v-if="url === ''" :src="url === '' ? placeholder : url" :style="`width:${width}px;height:${height}px;`" fit="fill">
|
||||
<template #error>
|
||||
<div class="image-slot" :style="`width:${width}px;height:${height}px;`">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:plus" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:plus" />
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
@@ -125,14 +113,10 @@ const onSuccess: UploadProps['onSuccess'] = (res) => {
|
||||
<div class="mask">
|
||||
<div class="actions">
|
||||
<span title="预览" @click.stop="preview">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:zoom-in" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:zoom-in" />
|
||||
</span>
|
||||
<span title="移除" @click.stop="remove">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:delete" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:delete" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -195,7 +179,7 @@ const onSuccess: UploadProps['onSuccess'] = (res) => {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
.icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +1,47 @@
|
||||
<script lang="ts" setup name="ImagesUpload">
|
||||
<script setup lang="ts">
|
||||
import type { UploadProps } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const props = defineProps({
|
||||
action: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
headers: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: 'file',
|
||||
},
|
||||
url: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
default: 3,
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 2,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 150,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 150,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
notip: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
ext: {
|
||||
type: Array,
|
||||
default: () => ['jpg', 'png', 'gif', 'bmp'],
|
||||
},
|
||||
defineOptions({
|
||||
name: 'ImagesUpload',
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:url', 'onSuccess'])
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
action: UploadProps['action']
|
||||
headers?: UploadProps['headers']
|
||||
data?: UploadProps['data']
|
||||
name?: UploadProps['name']
|
||||
url?: string[]
|
||||
size?: number
|
||||
max?: number
|
||||
width?: number
|
||||
height?: number
|
||||
placeholder?: string
|
||||
notip?: boolean
|
||||
ext?: string[]
|
||||
}>(),
|
||||
{
|
||||
name: 'file',
|
||||
url: () => [],
|
||||
size: 2,
|
||||
max: 3,
|
||||
width: 150,
|
||||
height: 150,
|
||||
placeholder: '',
|
||||
notip: false,
|
||||
ext: () => ['jpg', 'png', 'gif', 'bmp'],
|
||||
},
|
||||
)
|
||||
|
||||
const emits = defineEmits<{
|
||||
'update:url': [
|
||||
value: string[],
|
||||
]
|
||||
'onSuccess': [
|
||||
res: any,
|
||||
]
|
||||
}>()
|
||||
|
||||
const uploadData = ref({
|
||||
dialogImageIndex: 0,
|
||||
@@ -77,7 +65,7 @@ function previewClose() {
|
||||
function remove(index: number) {
|
||||
const url = props.url
|
||||
url.splice(index, 1)
|
||||
emit('update:url', url)
|
||||
emits('update:url', url)
|
||||
}
|
||||
// 移动
|
||||
function move(index: number, type: 'left' | 'right') {
|
||||
@@ -88,12 +76,12 @@ function move(index: number, type: 'left' | 'right') {
|
||||
if (type === 'right' && index !== url.length - 1) {
|
||||
url[index] = url.splice(index + 1, 1, url[index])[0]
|
||||
}
|
||||
emit('update:url', url)
|
||||
emits('update:url', url)
|
||||
}
|
||||
|
||||
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
|
||||
const fileName = file.name.split('.')
|
||||
const fileExt = fileName.at(-1)
|
||||
const fileExt = fileName.at(-1) ?? ''
|
||||
const isTypeOk = props.ext.includes(fileExt)
|
||||
const isSizeOk = file.size / 1024 / 1024 < props.size
|
||||
if (!isTypeOk) {
|
||||
@@ -113,7 +101,7 @@ const onProgress: UploadProps['onProgress'] = (file) => {
|
||||
const onSuccess: UploadProps['onSuccess'] = (res) => {
|
||||
uploadData.value.progress.preview = ''
|
||||
uploadData.value.progress.percent = 0
|
||||
emit('onSuccess', res)
|
||||
emits('onSuccess', res)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -124,24 +112,16 @@ const onSuccess: UploadProps['onSuccess'] = (res) => {
|
||||
<div class="mask">
|
||||
<div class="actions">
|
||||
<span title="预览" @click="preview(index)">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:zoom-in" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:zoom-in" />
|
||||
</span>
|
||||
<span title="移除" @click="remove(index)">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:delete" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:delete" />
|
||||
</span>
|
||||
<span v-show="url.length > 1" title="左移" :class="{ disabled: index === 0 }" @click="move(index, 'left')">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:back" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:back" />
|
||||
</span>
|
||||
<span v-show="url.length > 1" title="右移" :class="{ disabled: index === url.length - 1 }" @click="move(index, 'right')">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:right" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:right" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -160,9 +140,7 @@ const onSuccess: UploadProps['onSuccess'] = (res) => {
|
||||
class="images-upload"
|
||||
>
|
||||
<div class="image-slot" :style="`width:${width}px;height:${height}px;`">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:plus" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:plus" />
|
||||
</div>
|
||||
<div v-show="uploadData.progress.percent" class="progress" :style="`width:${width}px;height:${height}px;`">
|
||||
<el-image :src="uploadData.progress.preview" :style="`width:${width}px;height:${height}px;`" fit="fill" />
|
||||
@@ -230,7 +208,7 @@ const onSuccess: UploadProps['onSuccess'] = (res) => {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
.icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'NotAllowed',
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const data = ref({
|
||||
inter: NaN,
|
||||
inter: Number.NaN,
|
||||
countdown: 5,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
<script lang="ts" setup name="PageHeader">
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'PageHeader',
|
||||
})
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
title: string
|
||||
content?: string
|
||||
}>(),
|
||||
{
|
||||
content: '',
|
||||
},
|
||||
)
|
||||
|
||||
const slots = useSlots()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="header">
|
||||
<div class="page-header">
|
||||
<div class="main">
|
||||
<div class="title">
|
||||
{{ title }}
|
||||
@@ -32,18 +35,19 @@ const slots = useSlots()
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header {
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
padding: 16px 20px;
|
||||
background-color: var(--g-app-bg);
|
||||
transition: background-color 0.3s;
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
margin-right: 20px;
|
||||
flex: 1 1 70%;
|
||||
|
||||
.title {
|
||||
font-size: 22px;
|
||||
@@ -65,17 +69,7 @@ const slots = useSlots()
|
||||
|
||||
.sub {
|
||||
flex: none;
|
||||
}
|
||||
}
|
||||
|
||||
[data-mode="mobile"] {
|
||||
.header {
|
||||
flex-direction: column;
|
||||
|
||||
.main {
|
||||
margin-right: auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
<script lang="ts" setup name="PageMain">
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
collaspe: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'PageMain',
|
||||
})
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
title?: string
|
||||
collaspe?: boolean
|
||||
height?: string
|
||||
}>(),
|
||||
{
|
||||
title: '',
|
||||
collaspe: false,
|
||||
height: '',
|
||||
},
|
||||
)
|
||||
|
||||
const titleSlot = !!useSlots().title
|
||||
|
||||
const collaspeData = ref(props.collaspe)
|
||||
@@ -38,9 +40,7 @@ function unCollaspe() {
|
||||
</div>
|
||||
<slot />
|
||||
<div v-if="collaspeData" class="collaspe" title="展开" @click="unCollaspe">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:arrow-down" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:arrow-down" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,27 +1,36 @@
|
||||
<script lang="ts" setup name="PcasCascader">
|
||||
<script setup lang="ts">
|
||||
// 行政区划数据来源于 https://github.com/modood/Administrative-divisions-of-China
|
||||
import pcasRaw from './pcas-code.json'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
type: {
|
||||
type: String as () => 'pc' | 'pca' | 'pcas',
|
||||
default: 'pca',
|
||||
},
|
||||
format: {
|
||||
type: String as () => 'code' | 'name' | 'both',
|
||||
default: 'code',
|
||||
},
|
||||
defineOptions({
|
||||
name: 'PcasCascader',
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: string[] | {
|
||||
code: string
|
||||
name: string
|
||||
}[]
|
||||
disabled?: boolean
|
||||
type?: 'pc' | 'pca' | 'pcas'
|
||||
format?: 'code' | 'name' | 'both'
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
type: 'pca',
|
||||
format: 'code',
|
||||
},
|
||||
)
|
||||
|
||||
const emits = defineEmits<{
|
||||
'update:modelValue': [
|
||||
value: string[] | {
|
||||
code: string
|
||||
name: string
|
||||
}[],
|
||||
]
|
||||
}>()
|
||||
|
||||
interface pcasItem {
|
||||
code: string
|
||||
@@ -83,7 +92,7 @@ const myValue = computed({
|
||||
},
|
||||
// 将 code 码转成出参数据
|
||||
set: (value) => {
|
||||
emit('update:modelValue', value ? codeToAny(value) : [])
|
||||
emits('update:modelValue', value ? codeToAny(value) : [])
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,40 +1,52 @@
|
||||
<script lang="ts" setup name="SearchBar">
|
||||
const props = defineProps({
|
||||
showMore: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
unfold: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'SearchBar',
|
||||
})
|
||||
|
||||
const emit = defineEmits(['toggle'])
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
fold?: boolean
|
||||
showToggle?: boolean
|
||||
background?: boolean
|
||||
}>(),
|
||||
{
|
||||
fold: true,
|
||||
showToggle: true,
|
||||
background: false,
|
||||
},
|
||||
)
|
||||
|
||||
const isUnfold = ref(!props.unfold)
|
||||
const emits = defineEmits<{
|
||||
'update:fold': [
|
||||
value: boolean,
|
||||
]
|
||||
'toggle': [
|
||||
value: boolean,
|
||||
]
|
||||
}>()
|
||||
|
||||
watch(() => props.unfold, () => toggle(), {
|
||||
immediate: true,
|
||||
const isFold = ref(props.fold)
|
||||
|
||||
watch(() => props.fold, (value) => {
|
||||
isFold.value = value
|
||||
emits('update:fold', value)
|
||||
})
|
||||
|
||||
function toggle() {
|
||||
isUnfold.value = !isUnfold.value
|
||||
emit('toggle', isUnfold.value)
|
||||
isFold.value = !isFold.value
|
||||
emits('toggle', isFold.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="search-container">
|
||||
<slot />
|
||||
<div v-if="showMore" class="more">
|
||||
<div class="search-container" :class="{ 'has-bg': background }">
|
||||
<slot :fold="isFold" />
|
||||
<div v-if="showToggle" class="toggle">
|
||||
<el-button text size="small" @click="toggle">
|
||||
<template #icon>
|
||||
<el-icon>
|
||||
<svg-icon :name="isUnfold ? 'ep:caret-top' : 'ep:caret-bottom'" />
|
||||
</el-icon>
|
||||
<svg-icon :name="isFold ? 'ep:caret-bottom' : 'ep:caret-top' " />
|
||||
</template>
|
||||
{{ isUnfold ? '收起' : '展开' }}
|
||||
{{ isFold ? '展开' : '收起' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -43,17 +55,11 @@ function toggle() {
|
||||
<style lang="scss" scoped>
|
||||
.search-container {
|
||||
position: relative;
|
||||
margin: 20px 0;
|
||||
padding: 20px;
|
||||
background-color: var(--el-fill-color-lighter);
|
||||
transition: background-color 0.3s;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
&.has-bg {
|
||||
padding: 20px;
|
||||
background-color: var(--el-fill-color-lighter);
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
:deep(.el-form) {
|
||||
@@ -68,7 +74,7 @@ function toggle() {
|
||||
}
|
||||
}
|
||||
|
||||
.more {
|
||||
.toggle {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
margin-bottom: -10px;
|
||||
|
||||
@@ -1,47 +1,68 @@
|
||||
<script lang="ts" setup name="SvgIcon">
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
|
||||
const props = defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
flip: {
|
||||
type: String as () => 'horizontal' | 'vertical' | 'both' | '',
|
||||
default: '',
|
||||
},
|
||||
rotate: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
defineOptions({
|
||||
name: 'SvgIcon',
|
||||
})
|
||||
|
||||
const transformStyle = computed(() => {
|
||||
const style = []
|
||||
if (props.flip !== '') {
|
||||
const props = defineProps<{
|
||||
name: string
|
||||
flip?: 'horizontal' | 'vertical' | 'both'
|
||||
rotate?: number
|
||||
color?: string
|
||||
size?: string | number
|
||||
}>()
|
||||
|
||||
const style = computed(() => {
|
||||
const transform = []
|
||||
if (props.flip) {
|
||||
switch (props.flip) {
|
||||
case 'horizontal':
|
||||
style.push('rotateY(180deg)')
|
||||
transform.push('rotateY(180deg)')
|
||||
break
|
||||
case 'vertical':
|
||||
style.push('rotateX(180deg)')
|
||||
transform.push('rotateX(180deg)')
|
||||
break
|
||||
case 'both':
|
||||
style.push('rotateX(180deg)')
|
||||
style.push('rotateY(180deg)')
|
||||
transform.push('rotateX(180deg)')
|
||||
transform.push('rotateY(180deg)')
|
||||
break
|
||||
}
|
||||
}
|
||||
if (props.rotate !== 0) {
|
||||
style.push(`rotate(${props.rotate}deg)`)
|
||||
if (props.rotate) {
|
||||
transform.push(`rotate(${props.rotate % 360}deg)`)
|
||||
}
|
||||
return {
|
||||
...(props.color && { color: props.color }),
|
||||
...(props.size && { fontSize: typeof props.size === 'number' ? `${props.size}px` : props.size }),
|
||||
...(transform.length && { transform: transform.join(' ') }),
|
||||
}
|
||||
return `transform: ${style.join(' ')};`
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Icon v-if="name.indexOf('ep:') === 0" :icon="name" :style="transformStyle" />
|
||||
<svg v-else :style="transformStyle" aria-hidden="true">
|
||||
<use :xlink:href="`#icon-${name}`" />
|
||||
</svg>
|
||||
<i class="icon" :style="style">
|
||||
<Icon v-if="name.indexOf('ep:') === 0" :icon="name" />
|
||||
<svg v-else aria-hidden="true">
|
||||
<use :xlink:href="`#icon-${name}`" />
|
||||
</svg>
|
||||
</i>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icon {
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
line-height: 1em;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
fill: currentcolor;
|
||||
|
||||
svg {
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -16,9 +16,6 @@ onMounted(() => {
|
||||
<div>
|
||||
<el-drawer v-model="isShow" title="系统信息" direction="rtl" :size="360">
|
||||
<el-descriptions direction="vertical" :column="1" border>
|
||||
<el-descriptions-item label="版本号" align="center">
|
||||
{{ pkg.version }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="最后编译时间" align="center">
|
||||
{{ lastBuildTime }}
|
||||
</el-descriptions-item>
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
<script lang="ts" setup name="Trend">
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
type: String as () => 'up' | 'down',
|
||||
default: 'up',
|
||||
},
|
||||
prefix: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
suffix: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
reverse: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'Trend',
|
||||
})
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value: string
|
||||
type?: 'up' | 'down'
|
||||
prefix?: string
|
||||
suffix?: string
|
||||
reverse?: boolean
|
||||
}>(),
|
||||
{
|
||||
type: 'up',
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
reverse: false,
|
||||
},
|
||||
)
|
||||
|
||||
const isUp = computed(() => {
|
||||
let isUp = props.type === 'up'
|
||||
if (props.reverse) {
|
||||
@@ -36,9 +33,7 @@ const isUp = computed(() => {
|
||||
<span v-if="prefix" class="prefix">{{ prefix }}</span>
|
||||
<span class="text">{{ value }}</span>
|
||||
<span v-if="suffix" class="suffix">{{ suffix }}</span>
|
||||
<el-icon>
|
||||
<svg-icon name="ep:caret-top" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:caret-top" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -55,12 +50,12 @@ const isUp = computed(() => {
|
||||
&.down {
|
||||
color: var(--el-color-danger);
|
||||
|
||||
.el-icon {
|
||||
.icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
.icon {
|
||||
margin-left: 5px;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<script lang="ts" setup name="AppSetting">
|
||||
<script setup lang="ts">
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import eventBus from '@/utils/eventBus'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import useMenuStore from '@/store/modules/menu'
|
||||
|
||||
defineOptions({
|
||||
name: 'AppSetting',
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
@@ -47,9 +51,7 @@ function handleCopy() {
|
||||
<el-divider>颜色主题</el-divider>
|
||||
<div class="color-scheme">
|
||||
<div class="switch" :class="settingsStore.settings.app.colorScheme" @click="settingsStore.settings.app.colorScheme = settingsStore.settings.app.colorScheme === 'dark' ? 'light' : 'dark'">
|
||||
<el-icon class="icon">
|
||||
<svg-icon :name="settingsStore.settings.app.colorScheme === 'light' ? 'ep:sunny' : 'ep:moon'" />
|
||||
</el-icon>
|
||||
<svg-icon :name="settingsStore.settings.app.colorScheme === 'light' ? 'ep:sunny' : 'ep:moon'" />
|
||||
</div>
|
||||
</div>
|
||||
<el-divider v-if="settingsStore.mode === 'pc'">
|
||||
@@ -59,25 +61,19 @@ function handleCopy() {
|
||||
<el-tooltip content="侧边栏模式(含主导航)" placement="top" :show-after="500">
|
||||
<div class="mode mode-side" :class="{ active: settingsStore.settings.menu.menuMode === 'side' }" @click="settingsStore.settings.menu.menuMode = 'side'">
|
||||
<div class="mode-container" />
|
||||
<el-icon>
|
||||
<svg-icon name="ep:check" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:check" />
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="顶部模式" placement="top" :show-after="500">
|
||||
<div class="mode mode-head" :class="{ active: settingsStore.settings.menu.menuMode === 'head' }" @click="settingsStore.settings.menu.menuMode = 'head'">
|
||||
<div class="mode-container" />
|
||||
<el-icon>
|
||||
<svg-icon name="ep:check" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:check" />
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="侧边栏模式(不含主导航)" placement="top" :show-after="500">
|
||||
<div class="mode mode-single" :class="{ active: settingsStore.settings.menu.menuMode === 'single' }" @click="settingsStore.settings.menu.menuMode = 'single'">
|
||||
<div class="mode-container" />
|
||||
<el-icon>
|
||||
<svg-icon name="ep:check" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:check" />
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
@@ -86,9 +82,7 @@ function handleCopy() {
|
||||
<div class="label">
|
||||
主导航切换跳转
|
||||
<el-tooltip content="开启该功能后,切换主导航时,页面自动跳转至该主导航下,次导航里第一个导航" placement="top">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:question-filled" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-switch v-model="settingsStore.settings.menu.switchMainMenuAndPageJump" :disabled="['single'].includes(settingsStore.settings.menu.menuMode)" />
|
||||
@@ -97,9 +91,7 @@ function handleCopy() {
|
||||
<div class="label">
|
||||
次导航保持展开一个
|
||||
<el-tooltip content="开启该功能后,次导航只保持单个菜单的展开" placement="top">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:question-filled" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-switch v-model="settingsStore.settings.menu.subMenuUniqueOpened" />
|
||||
@@ -144,9 +136,7 @@ function handleCopy() {
|
||||
<div class="label">
|
||||
全屏
|
||||
<el-tooltip content="该功能使用场景极少,用户习惯于通过窗口“最大化”功能来扩大显示区域,以显示更多内容,并且使用 F11 键也可以进入全屏效果" placement="top">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:question-filled" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-switch v-model="settingsStore.settings.toolbar.enableFullscreen" />
|
||||
@@ -155,9 +145,7 @@ function handleCopy() {
|
||||
<div class="label">
|
||||
页面刷新
|
||||
<el-tooltip content="开启时会阻止原生 F5 键刷新功能,并采用框架提供的刷新模式进行页面刷新" placement="top">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:question-filled" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-switch v-model="settingsStore.settings.toolbar.enablePageReload" />
|
||||
@@ -166,9 +154,7 @@ function handleCopy() {
|
||||
<div class="label">
|
||||
颜色主题
|
||||
<el-tooltip content="开启后可在明亮/暗黑模式中切换" placement="top">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:question-filled" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-switch v-model="settingsStore.settings.toolbar.enableColorScheme" />
|
||||
@@ -187,9 +173,7 @@ function handleCopy() {
|
||||
<div class="label">
|
||||
是否启用
|
||||
<el-tooltip content="对导航进行快捷搜索" placement="top">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:question-filled" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-switch v-model="settingsStore.settings.navSearch.enable" />
|
||||
@@ -236,9 +220,7 @@ function handleCopy() {
|
||||
<div class="label">
|
||||
是否开启
|
||||
<el-tooltip content="该功能开启时,登录成功默认进入主页,反之则默认进入导航栏里第一个导航页面" placement="top">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:question-filled" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-switch v-model="settingsStore.settings.home.enable" />
|
||||
@@ -254,9 +236,7 @@ function handleCopy() {
|
||||
<div class="label">
|
||||
组件尺寸
|
||||
<el-tooltip content="全局设置 Element Plus 组件的默认尺寸大小" placement="top">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:question-filled" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-radio-group v-model="settingsStore.settings.app.elementSize" size="small">
|
||||
@@ -281,9 +261,7 @@ function handleCopy() {
|
||||
<div class="label">
|
||||
载入进度条
|
||||
<el-tooltip content="该功能开启时,跳转路由会看到页面顶部有进度条" placement="top">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:question-filled" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-switch v-model="settingsStore.settings.app.enableProgress" />
|
||||
@@ -292,9 +270,7 @@ function handleCopy() {
|
||||
<div class="label">
|
||||
动态标题
|
||||
<el-tooltip content="该功能开启时,页面标题会显示当前路由标题,格式为“页面标题 - 网站名称”;关闭时则显示网站名称,网站名称在项目根目录下 .env.* 文件里配置" placement="top">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:question-filled" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:question-filled" />
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-switch v-model="settingsStore.settings.app.enableDynamicTitle" />
|
||||
@@ -302,9 +278,7 @@ function handleCopy() {
|
||||
<template v-if="isSupported" #footer>
|
||||
<el-button type="primary" @click="handleCopy">
|
||||
<template #icon>
|
||||
<el-icon>
|
||||
<svg-icon name="ep:document-copy" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:document-copy" />
|
||||
</template>
|
||||
复制配置
|
||||
</el-button>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts" setup name="Copyright">
|
||||
<script setup lang="ts">
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
|
||||
defineOptions({
|
||||
name: 'Copyright',
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const settingsStore = useSettingsStore()
|
||||
</script>
|
||||
@@ -1,9 +1,13 @@
|
||||
<script lang="ts" setup name="Header">
|
||||
<script setup lang="ts">
|
||||
import Logo from '../Logo/index.vue'
|
||||
import Tools from '../Tools/index.vue'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import useMenuStore from '@/store/modules/menu'
|
||||
|
||||
defineOptions({
|
||||
name: 'LayoutHeader',
|
||||
})
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const menuStore = useMenuStore()
|
||||
|
||||
@@ -30,9 +34,7 @@ function handlerMouserScroll(event: WheelEvent) {
|
||||
<template v-for="(item, index) in menuStore.allMenus" :key="index">
|
||||
<div v-if="item.children && item.children.length !== 0" class="item-container" :class="{ active: index === menuStore.actived }">
|
||||
<div class="item" @click="switchTo(index)">
|
||||
<el-icon v-if="item.meta?.icon">
|
||||
<svg-icon :name="item.meta.icon" />
|
||||
</el-icon>
|
||||
<svg-icon v-if="item.meta?.icon" :name="item.meta.icon" />
|
||||
<span v-if="item.meta?.title">{{ item.meta.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -143,7 +145,7 @@ header {
|
||||
background-color: var(--g-header-menu-hover-bg);
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
.icon {
|
||||
font-size: 24px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
@@ -167,7 +169,7 @@ header {
|
||||
:deep(.tools) {
|
||||
padding: 0;
|
||||
|
||||
.buttons .item .el-icon {
|
||||
.buttons .item .icon {
|
||||
color: var(--g-header-color);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
import eventBus from '@/utils/eventBus'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
|
||||
defineOptions({
|
||||
name: 'HotkeysIntro',
|
||||
})
|
||||
|
||||
const isShow = ref(false)
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
<script lang="ts" setup name="Logo">
|
||||
<script setup lang="ts">
|
||||
import imgLogo from '@/assets/images/logo.png'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
|
||||
defineProps({
|
||||
showLogo: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
defineOptions({
|
||||
name: 'Logo',
|
||||
})
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
showLogo?: boolean
|
||||
showTitle?: boolean
|
||||
}>(),
|
||||
{
|
||||
showLogo: true,
|
||||
showTitle: true,
|
||||
},
|
||||
)
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
const title = ref(import.meta.env.VITE_APP_TITLE)
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<script lang="ts" setup name="MainSidebar">
|
||||
<script setup lang="ts">
|
||||
import Logo from '../Logo/index.vue'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import useMenuStore from '@/store/modules/menu'
|
||||
|
||||
defineOptions({
|
||||
name: 'MainSidebar',
|
||||
})
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const menuStore = useMenuStore()
|
||||
|
||||
@@ -21,9 +25,7 @@ const { switchTo } = useMenu()
|
||||
active: index === menuStore.actived,
|
||||
}" :title="item.meta?.title ?? '[ 无标题 ]'" @click="switchTo(index)"
|
||||
>
|
||||
<el-icon v-if="item.meta?.icon">
|
||||
<svg-icon :name="item.meta.icon" />
|
||||
</el-icon>
|
||||
<svg-icon v-if="item.meta?.icon" :name="item.meta.icon" />
|
||||
<span>{{ item.meta?.title ?? '[ 无标题 ]' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -85,7 +87,7 @@ const { switchTo } = useMenu()
|
||||
background-color: var(--g-main-sidebar-menu-active-bg);
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
.icon {
|
||||
margin: 0 auto;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="ts" setup name="Search">
|
||||
<script setup lang="ts">
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import hotkeys from 'hotkeys-js'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
@@ -8,6 +8,10 @@ import useRouteStore from '@/store/modules/route'
|
||||
import useMenuStore from '@/store/modules/menu'
|
||||
import type { Menu } from '#/global'
|
||||
|
||||
defineOptions({
|
||||
name: 'Search',
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
@@ -17,9 +21,9 @@ const menuStore = useMenuStore()
|
||||
interface listTypes {
|
||||
path: string
|
||||
icon?: string
|
||||
title?: string | Function
|
||||
title?: string | (() => string)
|
||||
link?: string
|
||||
breadcrumb: (string | Function | undefined)[]
|
||||
breadcrumb: any[]
|
||||
}
|
||||
|
||||
const isShow = ref(false)
|
||||
@@ -81,7 +85,7 @@ const resultList = computed(() => {
|
||||
|
||||
watch(() => isShow.value, (val) => {
|
||||
if (val) {
|
||||
document.body.classList.add('hidden')
|
||||
document.body.classList.add('overflow-hidden')
|
||||
searchResultRef.value.scrollTop = 0
|
||||
// 当搜索显示的时候绑定上、下、回车快捷键,隐藏的时候再解绑。另外当 input 处于 focus 状态时,采用 vue 来绑定键盘事件
|
||||
hotkeys('up', keyUp)
|
||||
@@ -92,7 +96,7 @@ watch(() => isShow.value, (val) => {
|
||||
}, 500)
|
||||
}
|
||||
else {
|
||||
document.body.classList.remove('hidden')
|
||||
document.body.classList.remove('overflow-hidden')
|
||||
hotkeys.unbind('up', keyUp)
|
||||
hotkeys.unbind('down', keyDown)
|
||||
hotkeys.unbind('enter', keyEnter)
|
||||
@@ -143,16 +147,16 @@ function hasChildren(item: RouteRecordRaw) {
|
||||
}
|
||||
return flag
|
||||
}
|
||||
function getSourceList(arr: RouteRecordRaw[], basePath?: string, icon?: string, breadcrumb?: (string | Function | undefined)[]) {
|
||||
function getSourceList(arr: RouteRecordRaw[], basePath?: string, icon?: string, breadcrumb?: (string | (() => string))[]) {
|
||||
arr.forEach((item) => {
|
||||
if (item.meta?.sidebar !== false) {
|
||||
const breadcrumbTemp = cloneDeep(breadcrumb) || []
|
||||
if (item.children && hasChildren(item)) {
|
||||
breadcrumbTemp.push(item.meta?.title)
|
||||
item.meta?.title && breadcrumbTemp.push(item.meta?.title)
|
||||
getSourceList(item.children, basePath ? [basePath, item.path].join('/') : item.path, item.meta?.icon ?? icon, breadcrumbTemp)
|
||||
}
|
||||
else {
|
||||
breadcrumbTemp.push(item.meta?.title)
|
||||
item.meta?.title && breadcrumbTemp.push(item.meta?.title)
|
||||
sourceList.value.push({
|
||||
path: basePath ? [basePath, item.path].join('/') : item.path,
|
||||
icon: item.meta?.icon ?? icon,
|
||||
@@ -164,15 +168,15 @@ function getSourceList(arr: RouteRecordRaw[], basePath?: string, icon?: string,
|
||||
}
|
||||
})
|
||||
}
|
||||
function getSourceListByMenus(arr: Menu.recordRaw[], icon?: string, breadcrumb?: (string | Function | undefined)[]) {
|
||||
function getSourceListByMenus(arr: Menu.recordRaw[], icon?: string, breadcrumb?: (string | (() => string))[]) {
|
||||
arr.forEach((item) => {
|
||||
const breadcrumbTemp = cloneDeep(breadcrumb) || []
|
||||
if (item.children && item.children.length > 0) {
|
||||
breadcrumbTemp.push(item.meta?.title)
|
||||
item.meta?.title && breadcrumbTemp.push(item.meta?.title)
|
||||
getSourceListByMenus(item.children, item.meta?.icon ?? icon, breadcrumbTemp)
|
||||
}
|
||||
else {
|
||||
breadcrumbTemp.push(item.meta?.title)
|
||||
item.meta?.title && breadcrumbTemp.push(item.meta?.title)
|
||||
sourceList.value.push({
|
||||
icon: item.meta?.icon ?? icon,
|
||||
title: item.meta?.title,
|
||||
@@ -242,9 +246,7 @@ function pageJump(path: listTypes['path'], link: listTypes['link']) {
|
||||
<div class="search-box" @click.stop>
|
||||
<el-input ref="searchInputRef" v-model="searchInput" placeholder="搜索页面,支持标题、URL模糊查询" clearable @keydown.esc="eventBus.emit('global-search-toggle')" @keydown.up.prevent="keyUp" @keydown.down.prevent="keyDown" @keydown.enter.prevent="keyEnter">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<svg-icon name="ep:search" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:search" />
|
||||
</template>
|
||||
</el-input>
|
||||
<div v-if="settingsStore.mode === 'pc'" class="tips">
|
||||
@@ -258,14 +260,10 @@ function pageJump(path: listTypes['path'], link: listTypes['link']) {
|
||||
</div>
|
||||
<div class="tip">
|
||||
<el-tag type="info" size="large">
|
||||
<el-icon>
|
||||
<svg-icon name="search-up" />
|
||||
</el-icon>
|
||||
<svg-icon name="search-up" />
|
||||
</el-tag>
|
||||
<el-tag type="info" size="large">
|
||||
<el-icon>
|
||||
<svg-icon name="search-down" />
|
||||
</el-icon>
|
||||
<svg-icon name="search-down" />
|
||||
</el-tag>
|
||||
<el-tag type="info" size="large">
|
||||
切换搜索结果
|
||||
@@ -273,9 +271,7 @@ function pageJump(path: listTypes['path'], link: listTypes['link']) {
|
||||
</div>
|
||||
<div class="tip">
|
||||
<el-tag type="info" size="large">
|
||||
<el-icon>
|
||||
<svg-icon name="search-enter" />
|
||||
</el-icon>
|
||||
<svg-icon name="search-enter" />
|
||||
</el-tag>
|
||||
<el-tag type="info" size="large">
|
||||
访问页面
|
||||
@@ -293,9 +289,7 @@ function pageJump(path: listTypes['path'], link: listTypes['link']) {
|
||||
</div>
|
||||
<div ref="searchResultRef" class="result">
|
||||
<a v-for="(item, index) in resultList" :key="item.path" :ref="setSearchResultItemRef" class="item" :class="{ actived: index === actived }" @click="pageJump(item.path, item.link)" @mouseover="actived = index">
|
||||
<el-icon class="icon">
|
||||
<svg-icon v-if="item.icon" :name="item.icon" />
|
||||
</el-icon>
|
||||
<svg-icon v-if="item.icon" :name="item.icon" />
|
||||
<div class="info">
|
||||
<div class="title">
|
||||
{{ item.title ?? '[ 无标题 ]' }}
|
||||
@@ -303,9 +297,7 @@ function pageJump(path: listTypes['path'], link: listTypes['link']) {
|
||||
<div class="breadcrumb">
|
||||
<span v-for="(bc, bcIndex) in item.breadcrumb" :key="bcIndex">
|
||||
{{ bc ?? '[ 无标题 ]' }}
|
||||
<el-icon>
|
||||
<svg-icon name="ep:arrow-right" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:arrow-right" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="path">{{ item.path }}</div>
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
<script lang="ts" setup name="SidebarItem">
|
||||
// import SidebarItem from './index.vue'
|
||||
<script setup lang="ts">
|
||||
import { resolveRoutePath } from '@/utils'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import type { Menu } from '#/global'
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object as () => Menu.recordRaw,
|
||||
required: true,
|
||||
},
|
||||
basePath: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
defineOptions({
|
||||
name: 'SidebarItem',
|
||||
})
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
item: Menu.recordRaw
|
||||
basePath?: string
|
||||
}>(),
|
||||
{
|
||||
basePath: '',
|
||||
},
|
||||
)
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
const hasChildren = computed(() => {
|
||||
@@ -36,21 +38,17 @@ const hasChildren = computed(() => {
|
||||
<router-link v-if="!hasChildren" v-slot="{ href, navigate, isActive, isExactActive }" custom :to="resolveRoutePath(basePath, item.path)">
|
||||
<a :href="item.meta?.link ? item.meta.link : href" :class="[isActive && 'router-link-active', isExactActive && 'router-link-exact-active']" :target="item.meta?.link ? '_blank' : '_self'" @click="navigate">
|
||||
<el-menu-item :title="item.meta?.title ?? '[ 无标题 ]'" :index="resolveRoutePath(basePath, item.path || '')">
|
||||
<el-icon v-if="item.meta?.icon" class="title-icon">
|
||||
<svg-icon :name="item.meta.icon" />
|
||||
</el-icon>
|
||||
<svg-icon v-if="item.meta?.icon" :name="item.meta.icon" class="title-icon" />
|
||||
<span class="title">{{ item.meta?.title ?? '[ 无标题 ]' }}</span>
|
||||
</el-menu-item>
|
||||
</a>
|
||||
</router-link>
|
||||
<el-sub-menu v-else :title="item.meta?.title ?? '[ 无标题 ]'" :index="settingsStore.settings.app.routeBaseOn !== 'filesystem' ? resolveRoutePath(basePath, item.path) : JSON.stringify(item)">
|
||||
<template #title>
|
||||
<el-icon v-if="item.meta?.icon" class="title-icon">
|
||||
<svg-icon :name="item.meta.icon" />
|
||||
</el-icon>
|
||||
<svg-icon v-if="item.meta?.icon" :name="item.meta.icon" class="title-icon" />
|
||||
<span class="title">{{ item.meta?.title ?? '[ 无标题 ]' }}</span>
|
||||
</template>
|
||||
<template v-for="route in item.children">
|
||||
<template v-for="route in (item.children as Menu.recordRaw[])">
|
||||
<SidebarItem v-if="route.meta?.sidebar !== false" :key="route.path" :item="route" :base-path="resolveRoutePath(basePath, item.path)" />
|
||||
</template>
|
||||
</el-sub-menu>
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
<script lang="ts" setup name="SubSidebar">
|
||||
<script setup lang="ts">
|
||||
import Logo from '../Logo/index.vue'
|
||||
import SidebarItem from '../SidebarItem/index.vue'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import useMenuStore from '@/store/modules/menu'
|
||||
|
||||
defineOptions({
|
||||
name: 'SubSidebar',
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const menuStore = useMenuStore()
|
||||
|
||||
@@ -22,18 +28,21 @@ function onSidebarScroll(e: Event) {
|
||||
'shadow': sidebarScrollTop,
|
||||
}"
|
||||
/>
|
||||
<!-- 侧边栏模式(无主导航) -->
|
||||
<el-menu
|
||||
:unique-opened="settingsStore.settings.menu.subMenuUniqueOpened" :default-openeds="menuStore.defaultOpenedPaths" :default-active="$route.meta.activeMenu || $route.path" :collapse="settingsStore.mode === 'pc' && settingsStore.settings.menu.subMenuCollapse" :collapse-transition="false" :class="{
|
||||
'is-collapse-without-logo': settingsStore.settings.menu.menuMode !== 'single' && settingsStore.settings.menu.subMenuCollapse,
|
||||
}"
|
||||
>
|
||||
<transition-group name="sub-sidebar">
|
||||
<template v-for="(route, index) in menuStore.sidebarMenus">
|
||||
<SidebarItem v-if="route.meta?.sidebar !== false" :key="route.path || index" :item="route" :base-path="route.path" />
|
||||
</template>
|
||||
</transition-group>
|
||||
</el-menu>
|
||||
<transition-group name="sub-sidebar">
|
||||
<template v-for="(mainItem, mainIndex) in menuStore.allMenus" :key="mainIndex">
|
||||
<div v-show="mainIndex === menuStore.actived">
|
||||
<el-menu
|
||||
:unique-opened="settingsStore.settings.menu.subMenuUniqueOpened" :default-openeds="menuStore.defaultOpenedPaths" :default-active="route.meta.activeMenu || route.path" :collapse="settingsStore.mode === 'pc' && settingsStore.settings.menu.subMenuCollapse" :collapse-transition="false" :class="{
|
||||
'is-collapse-without-logo': settingsStore.settings.menu.menuMode !== 'single' && settingsStore.settings.menu.subMenuCollapse,
|
||||
}"
|
||||
>
|
||||
<template v-for="(item, index) in mainItem.children">
|
||||
<SidebarItem v-if="item.meta?.sidebar !== false" :key="item.path || index" :item="item" :base-path="item.path" />
|
||||
</template>
|
||||
</el-menu>
|
||||
</div>
|
||||
</template>
|
||||
</transition-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
<script lang="ts" setup name="Tools">
|
||||
<script setup lang="ts">
|
||||
import { useFullscreen } from '@vueuse/core'
|
||||
import eventBus from '@/utils/eventBus'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
|
||||
defineOptions({
|
||||
name: 'Tools',
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
@@ -28,11 +32,7 @@ function userCommand(command: 'home' | 'setting' | 'hotkeys' | 'logout') {
|
||||
eventBus.emit('global-hotkeys-intro-toggle')
|
||||
break
|
||||
case 'logout':
|
||||
userStore.logout().then(() => {
|
||||
router.push({
|
||||
name: 'login',
|
||||
})
|
||||
})
|
||||
userStore.logout()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -42,38 +42,26 @@ function userCommand(command: 'home' | 'setting' | 'hotkeys' | 'logout') {
|
||||
<div class="tools">
|
||||
<div class="buttons">
|
||||
<span v-if="settingsStore.settings.navSearch.enable" class="item" @click="eventBus.emit('global-search-toggle')">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:search" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:search" />
|
||||
</span>
|
||||
<span v-if="settingsStore.mode === 'pc' && settingsStore.settings.toolbar.enableFullscreen" class="item" @click="toggle">
|
||||
<el-icon>
|
||||
<svg-icon :name="isFullscreen ? 'fullscreen-exit' : 'fullscreen'" />
|
||||
</el-icon>
|
||||
<svg-icon :name="isFullscreen ? 'fullscreen-exit' : 'fullscreen'" />
|
||||
</span>
|
||||
<span v-if="settingsStore.settings.toolbar.enablePageReload" class="item" @click="mainPage.reload()">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:refresh-right" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:refresh-right" />
|
||||
</span>
|
||||
<span v-if="settingsStore.settings.toolbar.enableColorScheme" class="item" @click="settingsStore.setColorScheme(settingsStore.settings.app.colorScheme === 'dark' ? 'light' : 'dark')">
|
||||
<el-icon>
|
||||
<svg-icon v-show="settingsStore.settings.app.colorScheme === 'light'" name="ep:sunny" />
|
||||
<svg-icon v-show="settingsStore.settings.app.colorScheme === 'dark'" name="ep:moon" />
|
||||
</el-icon>
|
||||
<svg-icon v-show="settingsStore.settings.app.colorScheme === 'light'" name="ep:sunny" />
|
||||
<svg-icon v-show="settingsStore.settings.app.colorScheme === 'dark'" name="ep:moon" />
|
||||
</span>
|
||||
</div>
|
||||
<el-dropdown class="user-container" size="default" @command="userCommand">
|
||||
<div class="user-wrapper">
|
||||
<el-avatar size="small">
|
||||
<el-icon>
|
||||
<svg-icon name="ep:user-filled" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:user-filled" />
|
||||
</el-avatar>
|
||||
{{ userStore.account }}
|
||||
<el-icon>
|
||||
<svg-icon name="ep:caret-bottom" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:caret-bottom" />
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu class="user-dropdown">
|
||||
@@ -114,7 +102,7 @@ function userCommand(command: 'home' | 'setting' | 'hotkeys' | 'logout') {
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
|
||||
.el-icon {
|
||||
.icon {
|
||||
color: var(--el-text-color-primary);
|
||||
transition: var(--el-transition-color);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<script lang="ts" setup name="Topbar">
|
||||
<script setup lang="ts">
|
||||
import { compile } from 'path-to-regexp'
|
||||
import Tools from '../Tools/index.vue'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
|
||||
defineOptions({
|
||||
name: 'Topbar',
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
@@ -40,7 +44,7 @@ function onScroll() {
|
||||
scrollTop.value = (document.documentElement || document.body).scrollTop
|
||||
}
|
||||
watch(scrollTop, (val, oldVal) => {
|
||||
const topbarHeight = parseInt(getComputedStyle(document.documentElement || document.body).getPropertyValue('--g-topbar-height'))
|
||||
const topbarHeight = Number.parseInt(getComputedStyle(document.documentElement || document.body).getPropertyValue('--g-topbar-height'))
|
||||
scrollOnHide.value = settingsStore.settings.topbar.mode === 'sticky' && val > oldVal && val > topbarHeight
|
||||
})
|
||||
|
||||
@@ -60,9 +64,7 @@ function pathCompile(path: string) {
|
||||
>
|
||||
<div class="left-box">
|
||||
<div v-if="enableSubMenuCollapseButton" class="sidebar-collapse" :class="{ 'is-collapse': settingsStore.settings.menu.subMenuCollapse }" @click="settingsStore.toggleSidebarCollapse()">
|
||||
<el-icon>
|
||||
<svg-icon name="toolbar-collapse" />
|
||||
</el-icon>
|
||||
<svg-icon name="toolbar-collapse" />
|
||||
</div>
|
||||
<el-breadcrumb v-if="settingsStore.settings.breadcrumb.enable && settingsStore.mode === 'pc' && settingsStore.settings.app.routeBaseOn !== 'filesystem'">
|
||||
<transition-group name="breadcrumb">
|
||||
@@ -115,16 +117,16 @@ function pathCompile(path: string) {
|
||||
height: 50px;
|
||||
cursor: pointer;
|
||||
|
||||
.el-icon {
|
||||
.icon {
|
||||
color: var(--el-text-color-primary);
|
||||
transition: var(--el-transition-color), var(--el-transition-md-fade);
|
||||
}
|
||||
|
||||
&:hover .el-icon {
|
||||
&:hover .icon {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
&.is-collapse .el-icon {
|
||||
&.is-collapse .icon {
|
||||
transform: rotateZ(-180deg);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<script lang="ts" setup name="LinkView">
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'LinkView',
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
function open() {
|
||||
@@ -19,9 +23,7 @@ function open() {
|
||||
</div>
|
||||
<el-button type="primary" plain round @click="open">
|
||||
<template #icon>
|
||||
<el-icon>
|
||||
<svg-icon name="ep:link" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:link" />
|
||||
</template>
|
||||
立即访问
|
||||
</el-button>
|
||||
|
||||
+10
-7
@@ -1,6 +1,5 @@
|
||||
<script lang="ts" setup name="Layout">
|
||||
<script setup lang="ts">
|
||||
import hotkeys from 'hotkeys-js'
|
||||
import LinkView from './components/views/link.vue'
|
||||
import Header from './components/Header/index.vue'
|
||||
import MainSidebar from './components/MainSidebar/index.vue'
|
||||
import SubSidebar from './components/SubSidebar/index.vue'
|
||||
@@ -8,11 +7,17 @@ import Topbar from './components/Topbar/index.vue'
|
||||
import Search from './components/Search/index.vue'
|
||||
import HotkeysIntro from './components/HotkeysIntro/index.vue'
|
||||
import AppSetting from './components/AppSetting/index.vue'
|
||||
import LinkView from './components/views/link.vue'
|
||||
import Copyright from './components/Copyright/index.vue'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import useKeepAliveStore from '@/store/modules/keepAlive'
|
||||
import useMenuStore from '@/store/modules/menu'
|
||||
import eventBus from '@/utils/eventBus'
|
||||
|
||||
defineOptions({
|
||||
name: 'Layout',
|
||||
})
|
||||
|
||||
const routeInfo = useRoute()
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
@@ -70,7 +75,7 @@ onUnmounted(() => {
|
||||
<SubSidebar />
|
||||
</div>
|
||||
<div class="sidebar-mask" :class="{ show: settingsStore.mode === 'mobile' && !settingsStore.settings.menu.subMenuCollapse }" @click="settingsStore.toggleSidebarCollapse()" />
|
||||
<div class="main-container" :style="{ 'padding-bottom': $route.meta.paddingBottom } as any">
|
||||
<div class="main-container" :style="{ 'padding-bottom': routeInfo.meta.paddingBottom } as any">
|
||||
<Topbar v-if="!(settingsStore.settings.menu.menuMode === 'head' && !settingsStore.settings.menu.enableSubMenuCollapseButton && !settingsStore.settings.breadcrumb.enable)" />
|
||||
<div class="main">
|
||||
<router-view v-slot="{ Component, route }">
|
||||
@@ -90,9 +95,7 @@ onUnmounted(() => {
|
||||
<Search />
|
||||
<HotkeysIntro />
|
||||
<div v-if="settingsStore.settings.app.enableAppSetting">
|
||||
<el-icon class="app-setting" @click="eventBus.emit('global-app-setting-toggle')">
|
||||
<svg-icon name="ep:setting" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:setting" class="app-setting" @click="eventBus.emit('global-app-setting-toggle')" />
|
||||
<AppSetting />
|
||||
</div>
|
||||
</div>
|
||||
@@ -242,7 +245,7 @@ header:not(.header-leave-active) + .wrapper {
|
||||
background-color: var(--el-color-primary);
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
:deep(svg) {
|
||||
animation: rotate 5s linear infinite;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,10 +7,13 @@ import useSettingsStore from './store/modules/settings'
|
||||
|
||||
// 自定义指令
|
||||
import directive from '@/utils/directive'
|
||||
|
||||
// 加载 svg 图标
|
||||
import 'virtual:svg-icons-register'
|
||||
|
||||
// 全局样式
|
||||
import '@/assets/styles/globals.scss'
|
||||
|
||||
// 加载 iconify 图标(element plus)
|
||||
import { downloadAndInstall } from '@/iconify-ep'
|
||||
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
export default [
|
||||
{
|
||||
url: '/mock/member/login',
|
||||
url: '/mock/user/login',
|
||||
method: 'post',
|
||||
response: (option: any) => {
|
||||
response: ({ body }: any) => {
|
||||
return {
|
||||
error: '',
|
||||
status: 1,
|
||||
data: {
|
||||
account: option.body.account,
|
||||
token: `${option.body.account}_@string`,
|
||||
account: body.account,
|
||||
token: `${body.account}_@string`,
|
||||
failure_time: Math.ceil(new Date().getTime() / 1000) + 24 * 60 * 60,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/mock/member/permission',
|
||||
url: '/mock/user/permission',
|
||||
method: 'get',
|
||||
response: (option: any) => {
|
||||
response: ({ headers }: any) => {
|
||||
let permissions: string[] = []
|
||||
if (option.headers.token.indexOf('admin') === 0) {
|
||||
if (headers.token.indexOf('admin') === 0) {
|
||||
permissions = [
|
||||
'permission.browse',
|
||||
'permission.create',
|
||||
@@ -27,7 +27,7 @@ export default [
|
||||
'permission.remove',
|
||||
]
|
||||
}
|
||||
else if (option.headers.token.indexOf('test') === 0) {
|
||||
else if (headers.token.indexOf('test') === 0) {
|
||||
permissions = [
|
||||
'permission.browse',
|
||||
]
|
||||
@@ -42,7 +42,7 @@ export default [
|
||||
},
|
||||
},
|
||||
{
|
||||
url: '/mock/member/edit/password',
|
||||
url: '/mock/user/password/edit',
|
||||
method: 'post',
|
||||
response: {
|
||||
error: '',
|
||||
+2
-1
@@ -2,6 +2,7 @@ import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { useNProgress } from '@vueuse/integrations/useNProgress'
|
||||
import '@/assets/styles/nprogress.scss'
|
||||
|
||||
// 路由相关数据
|
||||
import { asyncRoutes, asyncRoutesByFilesystem, constantRoutes, constantRoutesByFilesystem } from './routes'
|
||||
import pinia from '@/store'
|
||||
@@ -79,7 +80,7 @@ router.beforeEach(async (to, from, next) => {
|
||||
}
|
||||
// 注册并记录路由数据
|
||||
// 记录的数据会在登出时会使用到,不使用 router.removeRoute 是考虑配置的路由可能不一定有设置 name ,则通过调用 router.addRoute() 返回的回调进行删除
|
||||
const removeRoutes: Function[] = []
|
||||
const removeRoutes: (() => void)[] = []
|
||||
routeStore.flatRoutes.forEach((route) => {
|
||||
if (!/^(https?:|mailto:|tel:)/.test(route.path)) {
|
||||
removeRoutes.push(router.addRoute(route as RouteRecordRaw))
|
||||
|
||||
@@ -45,10 +45,10 @@ const globalSettingsDefault: RecursiveRequired<Settings.all> = {
|
||||
enableHotkeys: true,
|
||||
},
|
||||
copyright: {
|
||||
enable: true,
|
||||
dates: '2020-2023',
|
||||
company: 'Fantastic-admin',
|
||||
website: 'https://hooray.gitee.io/fantastic-admin/',
|
||||
enable: false,
|
||||
dates: '',
|
||||
company: '',
|
||||
website: '',
|
||||
beian: '',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,11 +2,10 @@ import { cloneDeep } from 'lodash-es'
|
||||
import useSettingsStore from './settings'
|
||||
import useUserStore from './user'
|
||||
import useRouteStore from './route'
|
||||
import type { Menu } from '#/global'
|
||||
|
||||
import { resolveRoutePath } from '@/utils'
|
||||
import api from '@/api'
|
||||
import apiApp from '@/api/modules/app'
|
||||
import menu from '@/menu'
|
||||
import type { Menu } from '#/global'
|
||||
|
||||
const useMenuStore = defineStore(
|
||||
// 唯一ID
|
||||
@@ -74,9 +73,11 @@ const useMenuStore = defineStore(
|
||||
}
|
||||
// 默认展开的导航路径
|
||||
const defaultOpenedPaths = computed(() => {
|
||||
let defaultOpenedPaths: string[] = []
|
||||
const defaultOpenedPaths: string[] = []
|
||||
if (settingsStore.settings.app.routeBaseOn !== 'filesystem') {
|
||||
defaultOpenedPaths = getDefaultOpenedPaths(sidebarMenus.value)
|
||||
allMenus.value.forEach((item) => {
|
||||
defaultOpenedPaths.push(...getDefaultOpenedPaths(item.children))
|
||||
})
|
||||
}
|
||||
return defaultOpenedPaths
|
||||
})
|
||||
@@ -119,8 +120,8 @@ const useMenuStore = defineStore(
|
||||
function filterAsyncMenus<T extends Menu.recordMainRaw[] | Menu.recordRaw[]>(menus: T, permissions: string[]): T {
|
||||
const res: any = []
|
||||
menus.forEach((menu) => {
|
||||
const tmpMenu = cloneDeep(menu)
|
||||
if (hasPermission(permissions, tmpMenu)) {
|
||||
if (hasPermission(permissions, menu)) {
|
||||
const tmpMenu = cloneDeep(menu)
|
||||
if (tmpMenu.children) {
|
||||
tmpMenu.children = filterAsyncMenus(tmpMenu.children, permissions) as Menu.recordRaw[]
|
||||
tmpMenu.children.length && res.push(tmpMenu)
|
||||
@@ -147,9 +148,7 @@ const useMenuStore = defineStore(
|
||||
}
|
||||
// 生成导航(后端生成)
|
||||
async function generateMenusAtBack() {
|
||||
await api.get('menu/list', {
|
||||
baseURL: '/mock/',
|
||||
}).then(async (res) => {
|
||||
await apiApp.menuList().then(async (res) => {
|
||||
let accessedMenus: Menu.recordMainRaw[]
|
||||
// 如果权限功能开启,则需要对导航数据进行筛选过滤
|
||||
if (settingsStore.settings.app.enablePermission) {
|
||||
|
||||
@@ -2,9 +2,9 @@ import { cloneDeep } from 'lodash-es'
|
||||
import type { RouteMeta, RouteRecordRaw } from 'vue-router'
|
||||
import useSettingsStore from './settings'
|
||||
import useUserStore from './user'
|
||||
import api from '@/api'
|
||||
import { resolveRoutePath } from '@/utils'
|
||||
import { systemRoutes } from '@/router/routes'
|
||||
import apiApp from '@/api/modules/app'
|
||||
import type { Route } from '#/global'
|
||||
|
||||
const useRouteStore = defineStore(
|
||||
@@ -17,7 +17,7 @@ const useRouteStore = defineStore(
|
||||
const isGenerate = ref(false)
|
||||
const routesRaw = ref<Route.recordMainRaw[]>([])
|
||||
const filesystemRoutesRaw = ref<RouteRecordRaw[]>([])
|
||||
const currentRemoveRoutes = ref<Function[]>([])
|
||||
const currentRemoveRoutes = ref<(() => void)[]>([])
|
||||
|
||||
// 将多层嵌套路由处理成两层,保留顶层和最子层路由,中间层级将被拍平
|
||||
function flatAsyncRoutes<T extends RouteRecordRaw>(route: T): T {
|
||||
@@ -196,9 +196,7 @@ const useRouteStore = defineStore(
|
||||
}
|
||||
// 根据权限动态生成路由(后端获取)
|
||||
async function generateRoutesAtBack() {
|
||||
await api.get('route/list', {
|
||||
baseURL: '/mock/',
|
||||
}).then(async (res) => {
|
||||
await apiApp.routeList().then(async (res) => {
|
||||
// 设置 routes 数据
|
||||
routesRaw.value = formatBackRoutes(res.data)
|
||||
if (settingsStore.settings.app.enablePermission) {
|
||||
@@ -218,7 +216,7 @@ const useRouteStore = defineStore(
|
||||
isGenerate.value = true
|
||||
}
|
||||
// 记录 accessRoutes 路由,用于登出时删除路由
|
||||
function setCurrentRemoveRoutes(routes: Function[]) {
|
||||
function setCurrentRemoveRoutes(routes: (() => void)[]) {
|
||||
currentRemoveRoutes.value = routes
|
||||
}
|
||||
// 清空动态路由
|
||||
|
||||
+13
-15
@@ -1,6 +1,7 @@
|
||||
import useRouteStore from './route'
|
||||
import useMenuStore from './menu'
|
||||
import api from '@/api'
|
||||
import router from '@/router'
|
||||
import apiUser from '@/api/modules/user'
|
||||
|
||||
const useUserStore = defineStore(
|
||||
// 唯一ID
|
||||
@@ -16,7 +17,7 @@ const useUserStore = defineStore(
|
||||
const isLogin = computed(() => {
|
||||
let retn = false
|
||||
if (token.value) {
|
||||
if (new Date().getTime() < parseInt(failure_time.value) * 1000) {
|
||||
if (new Date().getTime() < Number.parseInt(failure_time.value) * 1000) {
|
||||
retn = true
|
||||
}
|
||||
}
|
||||
@@ -29,9 +30,7 @@ const useUserStore = defineStore(
|
||||
password: string
|
||||
}) {
|
||||
// 通过 mock 进行登录
|
||||
const res = await api.post('member/login', data, {
|
||||
baseURL: '/mock/',
|
||||
})
|
||||
const res = await apiUser.login(data)
|
||||
localStorage.setItem('account', res.data.account)
|
||||
localStorage.setItem('token', res.data.token)
|
||||
localStorage.setItem('failure_time', res.data.failure_time)
|
||||
@@ -40,7 +39,7 @@ const useUserStore = defineStore(
|
||||
failure_time.value = res.data.failure_time
|
||||
}
|
||||
// 登出
|
||||
async function logout() {
|
||||
async function logout(redirect = router.currentRoute.value.fullPath) {
|
||||
localStorage.removeItem('account')
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('failure_time')
|
||||
@@ -49,13 +48,17 @@ const useUserStore = defineStore(
|
||||
failure_time.value = ''
|
||||
routeStore.removeRoutes()
|
||||
menuStore.setActived(0)
|
||||
router.push({
|
||||
name: 'login',
|
||||
query: {
|
||||
...(router.currentRoute.value.path !== '/' && router.currentRoute.value.name !== 'login' && { redirect }),
|
||||
},
|
||||
})
|
||||
}
|
||||
// 获取我的权限
|
||||
async function getPermissions() {
|
||||
// 通过 mock 获取权限
|
||||
const res = await api.get('member/permission', {
|
||||
baseURL: '/mock/',
|
||||
})
|
||||
const res = await apiUser.permission()
|
||||
permissions.value = res.data.permissions
|
||||
return permissions.value
|
||||
}
|
||||
@@ -64,12 +67,7 @@ const useUserStore = defineStore(
|
||||
password: string
|
||||
newpassword: string
|
||||
}) {
|
||||
await api.post('member/edit/password', {
|
||||
password: data.password,
|
||||
newpassword: data.newpassword,
|
||||
}, {
|
||||
baseURL: '/mock/',
|
||||
})
|
||||
await apiUser.passwordEdit(data)
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Vendored
+2
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
export {}
|
||||
declare global {
|
||||
@@ -59,6 +60,7 @@ declare global {
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const toValue: typeof import('vue')['toValue']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const unref: typeof import('vue')['unref']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
|
||||
Vendored
+1
-6
@@ -3,17 +3,12 @@
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
import '@vue/runtime-core'
|
||||
|
||||
export {}
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
Auth: typeof import('./../components/Auth/index.vue')['default']
|
||||
AuthAll: typeof import('./../components/AuthAll/index.vue')['default']
|
||||
BatchActionBar: typeof import('./../components/BatchActionBar/index.vue')['default']
|
||||
Copyright: typeof import('./../components/Copyright/index.vue')['default']
|
||||
Editor: typeof import('./../components/Editor/index.vue')['default']
|
||||
FileUpload: typeof import('./../components/FileUpload/index.vue')['default']
|
||||
FixedActionBar: typeof import('./../components/FixedActionBar/index.vue')['default']
|
||||
ImagePreview: typeof import('./../components/ImagePreview/index.vue')['default']
|
||||
|
||||
Vendored
+7
-7
@@ -163,22 +163,22 @@ declare namespace Settings {
|
||||
interface copyright {
|
||||
/**
|
||||
* 是否开启底部版权,同时在路由 meta 对象里可以单独设置某个路由是否显示底部版权信息
|
||||
* @默认值 `true`
|
||||
* @默认值 `false`
|
||||
*/
|
||||
enable?: boolean
|
||||
/**
|
||||
* 网站运行日期
|
||||
* @默认值 `'2020-2022'`
|
||||
* @默认值 `''`
|
||||
*/
|
||||
dates?: string
|
||||
/**
|
||||
* 公司名称
|
||||
* @默认值 `'Fantastic-admin'`
|
||||
* @默认值 `''`
|
||||
*/
|
||||
company?: string
|
||||
/**
|
||||
* 网站地址
|
||||
* @默认值 `'https://hooray.gitee.io/fantastic-admin/'`
|
||||
* @默认值 `''`
|
||||
*/
|
||||
website?: string
|
||||
/**
|
||||
@@ -211,7 +211,7 @@ declare namespace Settings {
|
||||
|
||||
declare module 'vue-router' {
|
||||
interface RouteMeta {
|
||||
title?: string | Function
|
||||
title?: string | (() => string)
|
||||
i18n?: string
|
||||
icon?: string
|
||||
activeIcon?: string
|
||||
@@ -229,7 +229,7 @@ declare module 'vue-router' {
|
||||
declare namespace Route {
|
||||
interface recordMainRaw {
|
||||
meta?: {
|
||||
title?: string | Function
|
||||
title?: string | (() => string)
|
||||
icon?: string
|
||||
auth?: string | string[]
|
||||
}
|
||||
@@ -237,7 +237,7 @@ declare namespace Route {
|
||||
}
|
||||
interface breadcrumb {
|
||||
path: string
|
||||
title?: string | Function
|
||||
title?: string | (() => string)
|
||||
hide: boolean
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+13
-20
@@ -1,20 +1,13 @@
|
||||
declare interface Window {
|
||||
webkitDevicePixelRatio: any
|
||||
mozDevicePixelRatio: any
|
||||
}
|
||||
|
||||
declare module '*.vue' {
|
||||
import { type DefineComponent } from 'vue'
|
||||
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
|
||||
declare const __SYSTEM_INFO__: {
|
||||
pkg: {
|
||||
version: string
|
||||
dependencies: Recordable<string>
|
||||
devDependencies: Recordable<string>
|
||||
}
|
||||
lastBuildTime: string
|
||||
}
|
||||
declare interface Window {
|
||||
webkitDevicePixelRatio: any
|
||||
mozDevicePixelRatio: any
|
||||
}
|
||||
|
||||
declare const __SYSTEM_INFO__: {
|
||||
pkg: {
|
||||
version: string
|
||||
dependencies: Recordable<string>
|
||||
devDependencies: Recordable<string>
|
||||
}
|
||||
lastBuildTime: string
|
||||
}
|
||||
|
||||
+13
-1
@@ -1,3 +1,15 @@
|
||||
import mitt from 'mitt'
|
||||
|
||||
export default mitt()
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
type Events = {
|
||||
// 应用设置弹窗
|
||||
'global-app-setting-toggle': undefined
|
||||
// 系统信息弹窗
|
||||
'global-system-info-toggle': undefined
|
||||
// 快捷键弹窗
|
||||
'global-hotkeys-intro-toggle': undefined
|
||||
// 搜索框弹窗
|
||||
'global-search-toggle': undefined
|
||||
}
|
||||
|
||||
export default mitt<Events>()
|
||||
|
||||
@@ -5,10 +5,10 @@ if (import.meta.env.PROD) {
|
||||
const copyright_main_style = `${copyright_common_style} background: #e24329;`
|
||||
const copyright_sub_style = `${copyright_common_style} background: #707070;`
|
||||
if ((navigator.language).toLowerCase() === 'zh-cn') {
|
||||
console.info('%c由%cFantastic-admin%c驱动', copyright_sub_style, copyright_main_style, copyright_sub_style, '\nhttps://gitee.com/hooray/fantastic-admin')
|
||||
console.info('%c由%cFantastic-admin%c驱动', copyright_sub_style, copyright_main_style, copyright_sub_style, '\nhttps://fantastic-admin.gitee.io')
|
||||
}
|
||||
else {
|
||||
console.info('%cPowered by%cFantastic-admin', copyright_sub_style, copyright_main_style, '\nhttps://github.com/hooray/fantastic-admin')
|
||||
console.info('%cPowered by%cFantastic-admin', copyright_sub_style, copyright_main_style, '\nhttps://fantastic-admin.github.io')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@ meta:
|
||||
layout: false
|
||||
</route>
|
||||
|
||||
<script lang="ts" setup>
|
||||
<script setup lang="ts">
|
||||
const router = useRouter()
|
||||
|
||||
const data = ref({
|
||||
inter: NaN,
|
||||
inter: Number.NaN,
|
||||
countdown: 5,
|
||||
})
|
||||
|
||||
|
||||
+123
-130
@@ -4,7 +4,7 @@ meta:
|
||||
title: 主页
|
||||
</route>
|
||||
|
||||
<script lang="ts" setup>
|
||||
<script setup lang="ts">
|
||||
function open(url: string) {
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
@@ -26,12 +26,12 @@ const fantasticAdminInfo = ref({
|
||||
imageVisible: false,
|
||||
index: 0,
|
||||
data: [
|
||||
'https://hooray.gitee.io/fantastic-admin/preview1.png',
|
||||
'https://hooray.gitee.io/fantastic-admin/preview2.png',
|
||||
'https://hooray.gitee.io/fantastic-admin/preview3.png',
|
||||
'https://hooray.gitee.io/fantastic-admin/preview4.png',
|
||||
'https://hooray.gitee.io/fantastic-admin/preview5.png',
|
||||
'https://hooray.gitee.io/fantastic-admin/preview6.png',
|
||||
'https://fantastic-admin.gitee.io/preview1.png',
|
||||
'https://fantastic-admin.gitee.io/preview2.png',
|
||||
'https://fantastic-admin.gitee.io/preview3.png',
|
||||
'https://fantastic-admin.gitee.io/preview4.png',
|
||||
'https://fantastic-admin.gitee.io/preview5.png',
|
||||
'https://fantastic-admin.gitee.io/preview6.png',
|
||||
],
|
||||
})
|
||||
|
||||
@@ -39,12 +39,12 @@ const oneStepAdminInfo = ref({
|
||||
imageVisible: false,
|
||||
index: 0,
|
||||
data: [
|
||||
'https://hooray.gitee.io/one-step-admin/preview1.png',
|
||||
'https://hooray.gitee.io/one-step-admin/preview2.png',
|
||||
'https://hooray.gitee.io/one-step-admin/preview3.png',
|
||||
'https://hooray.gitee.io/one-step-admin/preview4.png',
|
||||
'https://hooray.gitee.io/one-step-admin/preview5.png',
|
||||
'https://hooray.gitee.io/one-step-admin/preview6.png',
|
||||
'https://one-step-admin.gitee.io/preview1.png',
|
||||
'https://one-step-admin.gitee.io/preview2.png',
|
||||
'https://one-step-admin.gitee.io/preview3.png',
|
||||
'https://one-step-admin.gitee.io/preview4.png',
|
||||
'https://one-step-admin.gitee.io/preview5.png',
|
||||
'https://one-step-admin.gitee.io/preview6.png',
|
||||
],
|
||||
})
|
||||
</script>
|
||||
@@ -61,22 +61,20 @@ const oneStepAdminInfo = ref({
|
||||
</div>
|
||||
</template>
|
||||
<el-button-group>
|
||||
<el-button type="success" size="large" plain @click="open('https://hooray.gitee.io/fantastic-admin/')">
|
||||
<el-button type="success" size="large" plain @click="open('https://fantastic-admin.gitee.io')">
|
||||
开发文档
|
||||
</el-button>
|
||||
<el-dropdown>
|
||||
<el-button type="primary" size="large">
|
||||
代码仓库
|
||||
<el-icon class="el-icon--right">
|
||||
<svg-icon name="ep:arrow-down" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:arrow-down" class="el-icon--right" />
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="open('https://gitee.com/hooray/fantastic-admin')">
|
||||
<el-dropdown-item @click="open('https://gitee.com/fantastic-admin/basic')">
|
||||
Gitee
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="open('https://github.com/hooray/fantastic-admin')">
|
||||
<el-dropdown-item @click="open('https://github.com/fantastic-admin/basic')">
|
||||
Github
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
@@ -86,110 +84,89 @@ const oneStepAdminInfo = ref({
|
||||
</page-header>
|
||||
<el-row :gutter="20" style="margin: 0 10px;">
|
||||
<el-col :lg="8">
|
||||
<page-main style="margin: 0;">
|
||||
<div class="ecology vue">
|
||||
<div class="main">
|
||||
<img src="https://cn.vuejs.org/logo.svg" style="width: 30%;">
|
||||
<h1>Fantastic-startkit</h1>
|
||||
<h2>一款开箱即用的 Vue 项目启动套件</h2>
|
||||
<page-main class="ecology vue">
|
||||
<template #title>
|
||||
<div class="title-info">
|
||||
<img src="https://cn.vuejs.org/logo.svg">
|
||||
<div>
|
||||
<el-button type="primary" plain @click="open('https://hooray.gitee.io/fantastic-startkit/')">
|
||||
<template #icon>
|
||||
<el-icon>
|
||||
<svg-icon name="ep:link" />
|
||||
</el-icon>
|
||||
</template>
|
||||
访问官网
|
||||
</el-button>
|
||||
<el-button plain @click="open('https://hooray.github.io/fantastic-startkit/')">
|
||||
<template #icon>
|
||||
<el-icon>
|
||||
<svg-icon name="ep:link" />
|
||||
</el-icon>
|
||||
</template>
|
||||
备用地址
|
||||
</el-button>
|
||||
<h1>Fantastic-startkit</h1>
|
||||
<h2>一款开箱即用的 Vue 项目启动套件</h2>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider border-style="dashed" />
|
||||
<ul>
|
||||
<li v-for="item in fantasticStartkitInfo.feature" :key="item">
|
||||
{{ item }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<el-dropdown split-button type="primary" @click="open('https://hooray.gitee.io/fantastic-startkit/')">
|
||||
访问官网
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="open('https://hooray.github.io/fantastic-startkit/')">
|
||||
备用地址
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
<ul>
|
||||
<li v-for="item in fantasticStartkitInfo.feature" :key="item">
|
||||
{{ item }}
|
||||
</li>
|
||||
</ul>
|
||||
</page-main>
|
||||
</el-col>
|
||||
<el-col :lg="8">
|
||||
<page-main style="margin: 0;">
|
||||
<div class="ecology fa">
|
||||
<div class="main">
|
||||
<img src="https://hooray.gitee.io/fantastic-admin/logo.png" style="width: 30%;">
|
||||
<h1>Fantastic-admin</h1>
|
||||
<h2>一款开箱即用的 Vue 中后台管理系统框架</h2>
|
||||
<page-main class="ecology fa">
|
||||
<template #title>
|
||||
<div class="title-info">
|
||||
<img src="https://fantastic-admin.gitee.io/logo.png">
|
||||
<div>
|
||||
<el-button type="primary" plain @click="open('https://hooray.gitee.io/fantastic-admin/')">
|
||||
<template #icon>
|
||||
<el-icon>
|
||||
<svg-icon name="ep:link" />
|
||||
</el-icon>
|
||||
</template>
|
||||
访问官网
|
||||
</el-button>
|
||||
<el-button plain @click="open('https://hooray.github.io/fantastic-admin/')">
|
||||
<template #icon>
|
||||
<el-icon>
|
||||
<svg-icon name="ep:link" />
|
||||
</el-icon>
|
||||
</template>
|
||||
备用地址
|
||||
</el-button>
|
||||
<h1>Fantastic-admin</h1>
|
||||
<h2>一款开箱即用的 Vue 中后台管理系统框架</h2>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider border-style="dashed" />
|
||||
<el-carousel trigger="click" indicator-position="none" :interval="5000" height="250px">
|
||||
<el-carousel-item v-for="(item, index) in fantasticAdminInfo.data" :key="item">
|
||||
<el-image :src="item" fit="cover" style="cursor: pointer; width: 100%; height: 250px; margin: auto;" @click="fantasticAdminInfo.imageVisible = true; fantasticAdminInfo.index = index" />
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
<el-image-viewer v-if="fantasticAdminInfo.imageVisible" :url-list="fantasticAdminInfo.data" :initial-index="fantasticAdminInfo.index" @close="fantasticAdminInfo.imageVisible = false" />
|
||||
</div>
|
||||
<el-dropdown split-button type="primary" @click="open('https://fantastic-admin.gitee.io')">
|
||||
访问官网
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="open('https://fantastic-admin.github.io')">
|
||||
备用地址
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
<el-carousel trigger="click" indicator-position="none" :interval="5000" height="250px">
|
||||
<el-carousel-item v-for="(item, index) in fantasticAdminInfo.data" :key="item">
|
||||
<el-image :src="item" fit="cover" style="cursor: pointer; width: 100%; height: 250px; margin: auto;" @click="fantasticAdminInfo.imageVisible = true; fantasticAdminInfo.index = index" />
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
<el-image-viewer v-if="fantasticAdminInfo.imageVisible" :url-list="fantasticAdminInfo.data" :initial-index="fantasticAdminInfo.index" @close="fantasticAdminInfo.imageVisible = false" />
|
||||
</page-main>
|
||||
</el-col>
|
||||
<el-col :lg="8">
|
||||
<page-main style="margin: 0;">
|
||||
<div class="ecology osa">
|
||||
<div class="main">
|
||||
<img src="https://hooray.gitee.io/one-step-admin/logo.png" style="width: 30%;">
|
||||
<h1>One-step-admin</h1>
|
||||
<h2>一款干啥都快人一步的 Vue 中后台系统框架</h2>
|
||||
<page-main class="ecology osa">
|
||||
<template #title>
|
||||
<div class="title-info">
|
||||
<img src="https://one-step-admin.gitee.io/logo.png">
|
||||
<div>
|
||||
<el-button type="primary" plain @click="open('https://hooray.gitee.io/one-step-admin/')">
|
||||
<template #icon>
|
||||
<el-icon>
|
||||
<svg-icon name="ep:link" />
|
||||
</el-icon>
|
||||
</template>
|
||||
访问官网
|
||||
</el-button>
|
||||
<el-button plain @click="open('https://hooray.github.io/one-step-admin/')">
|
||||
<template #icon>
|
||||
<el-icon>
|
||||
<svg-icon name="ep:link" />
|
||||
</el-icon>
|
||||
</template>
|
||||
备用地址
|
||||
</el-button>
|
||||
<h1>One-step-admin</h1>
|
||||
<h2>一款干啥都快人一步的 Vue 中后台系统框架</h2>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider border-style="dashed" />
|
||||
<el-carousel trigger="click" indicator-position="none" :interval="5000" height="250px">
|
||||
<el-carousel-item v-for="(item, index) in oneStepAdminInfo.data" :key="item">
|
||||
<el-image :src="item" fit="cover" style="cursor: pointer; width: 100%; height: 250px; margin: auto;" @click="oneStepAdminInfo.imageVisible = true; oneStepAdminInfo.index = index" />
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
<el-image-viewer v-if="oneStepAdminInfo.imageVisible" :url-list="oneStepAdminInfo.data" :initial-index="oneStepAdminInfo.index" @close="oneStepAdminInfo.imageVisible = false" />
|
||||
</div>
|
||||
<el-dropdown split-button type="primary" @click="open('https://one-step-admin.gitee.io')">
|
||||
访问官网
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="open('https://one-step-admin.github.io')">
|
||||
备用地址
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
<el-carousel trigger="click" indicator-position="none" :interval="5000" height="250px">
|
||||
<el-carousel-item v-for="(item, index) in oneStepAdminInfo.data" :key="item">
|
||||
<el-image :src="item" fit="cover" style="cursor: pointer; width: 100%; height: 250px; margin: auto;" @click="oneStepAdminInfo.imageVisible = true; oneStepAdminInfo.index = index" />
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
<el-image-viewer v-if="oneStepAdminInfo.imageVisible" :url-list="oneStepAdminInfo.data" :initial-index="oneStepAdminInfo.index" @close="oneStepAdminInfo.imageVisible = false" />
|
||||
</page-main>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -224,7 +201,44 @@ const oneStepAdminInfo = ref({
|
||||
}
|
||||
|
||||
.ecology {
|
||||
padding: 10px 0 0;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
|
||||
:deep(.title-container) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
|
||||
.title-info {
|
||||
flex: 1 1 400px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
|
||||
img {
|
||||
width: 50px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.el-dropdown {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.vue {
|
||||
h1 {
|
||||
@@ -244,27 +258,6 @@ const oneStepAdminInfo = ref({
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 10px auto 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
color: var(--el-text-color-secondary);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.el-carousel {
|
||||
box-shadow: var(--el-box-shadow-light);
|
||||
transition: var(--el-transition-box-shadow);
|
||||
|
||||
+15
-28
@@ -5,11 +5,16 @@ meta:
|
||||
layout: false
|
||||
</route>
|
||||
|
||||
<script lang="ts" setup name="Login">
|
||||
<script setup lang="ts">
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import Copyright from '@/layouts/components/Copyright/index.vue'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
|
||||
defineOptions({
|
||||
name: 'Login',
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
@@ -160,18 +165,14 @@ function testAccount(account: string) {
|
||||
<el-form-item prop="account">
|
||||
<el-input v-model="loginForm.account" placeholder="用户名" text tabindex="1" autocomplete="on">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<svg-icon name="ep:user" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:user" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input v-model="loginForm.password" type="password" placeholder="密码" tabindex="2" autocomplete="on" show-password @keyup.enter="handleLogin">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<svg-icon name="ep:lock" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:lock" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
@@ -213,18 +214,14 @@ function testAccount(account: string) {
|
||||
<el-form-item prop="account">
|
||||
<el-input v-model="registerForm.account" placeholder="用户名" tabindex="1" autocomplete="on">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<svg-icon name="ep:user" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:user" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="captcha">
|
||||
<el-input v-model="registerForm.captcha" placeholder="验证码" tabindex="2" autocomplete="on">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<svg-icon name="ep:key" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:key" />
|
||||
</template>
|
||||
<template #append>
|
||||
<el-button>发送验证码</el-button>
|
||||
@@ -234,18 +231,14 @@ function testAccount(account: string) {
|
||||
<el-form-item prop="password">
|
||||
<el-input v-model="registerForm.password" type="password" placeholder="密码" tabindex="3" autocomplete="on" show-password>
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<svg-icon name="ep:lock" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:lock" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="checkPassword">
|
||||
<el-input v-model="registerForm.checkPassword" type="password" placeholder="确认密码" tabindex="4" autocomplete="on" show-password>
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<svg-icon name="ep:lock" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:lock" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
@@ -270,18 +263,14 @@ function testAccount(account: string) {
|
||||
<el-form-item prop="account">
|
||||
<el-input v-model="resetForm.account" placeholder="用户名" tabindex="1" autocomplete="on">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<svg-icon name="ep:user" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:user" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="captcha">
|
||||
<el-input v-model="resetForm.captcha" placeholder="验证码" tabindex="2" autocomplete="on">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<svg-icon name="ep:key" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:key" />
|
||||
</template>
|
||||
<template #append>
|
||||
<el-button>发送验证码</el-button>
|
||||
@@ -291,9 +280,7 @@ function testAccount(account: string) {
|
||||
<el-form-item prop="newPassword">
|
||||
<el-input v-model="resetForm.newPassword" type="password" placeholder="新密码" tabindex="3" autocomplete="on" show-password>
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<svg-icon name="ep:lock" />
|
||||
</el-icon>
|
||||
<svg-icon name="ep:lock" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
@@ -4,13 +4,14 @@ meta:
|
||||
title: 修改密码
|
||||
</route>
|
||||
|
||||
<script lang="ts" setup name="PersonalEditPassword">
|
||||
<script setup lang="ts">
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
defineOptions({
|
||||
name: 'PersonalEditPassword',
|
||||
})
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
@@ -52,14 +53,7 @@ function onSubmit() {
|
||||
type: 'success',
|
||||
message: '模拟修改成功,请重新登录',
|
||||
})
|
||||
userStore.logout().then(() => {
|
||||
router.push({
|
||||
name: 'login',
|
||||
query: {
|
||||
redirect: route.fullPath,
|
||||
},
|
||||
})
|
||||
})
|
||||
userStore.logout()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -5,10 +5,13 @@ meta:
|
||||
cache: personal-edit.password
|
||||
</route>
|
||||
|
||||
<script lang="ts" setup name="PersonalSetting">
|
||||
import type { UploadProps } from 'element-plus'
|
||||
<script setup lang="ts">
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
defineOptions({
|
||||
name: 'PersonalSetting',
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const form = ref({
|
||||
@@ -19,7 +22,7 @@ const form = ref({
|
||||
wechat: '',
|
||||
})
|
||||
|
||||
const handleSuccess: UploadProps['onSuccess'] = (res) => {
|
||||
function handleSuccess(res: any) {
|
||||
if (res.error === '') {
|
||||
form.value.headimg = res.data.path
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
name: reload
|
||||
</route>
|
||||
|
||||
<script lang="ts" setup>
|
||||
<script setup lang="ts">
|
||||
const router = useRouter()
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -5,8 +5,8 @@ export default function createBanner() {
|
||||
/**
|
||||
* 由 Fantastic-admin 提供技术支持
|
||||
* Powered by Fantastic-admin
|
||||
* Gitee https://gitee.com/hooray/fantastic-admin
|
||||
* Github https://github.com/hooray/fantastic-admin
|
||||
* Gitee https://fantastic-admin.gitee.io
|
||||
* Github https://fantastic-admin.github.io
|
||||
*/
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { PluginOption } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import vueLegacy from '@vitejs/plugin-legacy'
|
||||
|
||||
import createInspector from './inspector'
|
||||
import createAutoImport from './auto-import'
|
||||
import createComponents from './components'
|
||||
import createSetupExtend from './setup-extend'
|
||||
import createSvgIcon from './svg-icon'
|
||||
import createMock from './mock'
|
||||
import createLayouts from './layouts'
|
||||
@@ -18,11 +18,17 @@ export default function createVitePlugins(viteEnv, isBuild = false) {
|
||||
const vitePlugins: (PluginOption | PluginOption[])[] = [
|
||||
vue(),
|
||||
vueJsx(),
|
||||
vueLegacy({
|
||||
renderLegacyChunks: false,
|
||||
modernPolyfills: [
|
||||
'es.array.at',
|
||||
'es.array.find-last',
|
||||
],
|
||||
}),
|
||||
]
|
||||
vitePlugins.push(createInspector())
|
||||
vitePlugins.push(createAutoImport())
|
||||
vitePlugins.push(createComponents())
|
||||
vitePlugins.push(createSetupExtend())
|
||||
vitePlugins.push(createSvgIcon(isBuild))
|
||||
vitePlugins.push(createMock(viteEnv, isBuild))
|
||||
vitePlugins.push(createLayouts())
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import setupExtend from 'unplugin-vue-setup-extend-plus/vite'
|
||||
|
||||
export default function createSetupExtend() {
|
||||
return setupExtend({})
|
||||
}
|
||||
Reference in New Issue
Block a user