Compare commits

...
23 Commits
Author SHA1 Message Date
Hooray Hu 606f6c3e14 chore: release v3.1.0 2023-05-23 23:16:15 +08:00
Hooray Hu 6632ffbc65 refactor: 统一调整 defineEmits 写法 2023-05-23 23:15:12 +08:00
Hooray Hu dc625c3c26 chore: 更新依赖 2023-05-23 23:07:37 +08:00
Hooray Hu 84e0630dd0 fix: 移除模板代码中的 $route 2023-05-13 01:59:50 +08:00
Hooray Hu d4c465734f refactor: 调整 <script setup> 属性顺序 2023-05-13 00:42:59 +08:00
Hooray Hu ba8683a66e refactor: 统一调整 defineProps 以及默认值的写法 2023-05-13 00:36:00 +08:00
Hooray Hu bcbe576000 refactor!: 使用 defineOptions 设置组件 name
BREAKING CHANGE: 移除 `unplugin-vue-setup-extend-plus` 依赖
2023-05-13 00:14:34 +08:00
Hooray Hu 198dd88e3e chore: 更新依赖 2023-05-11 22:34:24 +08:00
Hooray Hu 417508426f feat: 引入vite legacy插件 2023-05-10 11:03:50 +08:00
Hooray Hu 40aa56c064 chore: 更新依赖 2023-05-08 09:02:18 +08:00
Hooray Hu e18c4b257a refactor: 重构次导航默认展开效果 2023-05-06 17:39:35 +08:00
Hooray Hu e1ac42629b chore: 修改mock请求地址 2023-05-06 17:39:03 +08:00
Hooray Hu 303aa75fd6 refactor: 优化 PageHeader 组件样式 2023-05-05 23:38:57 +08:00
Hooray Hu a2d5294285 chore: release v3.0.0 2023-05-02 03:01:14 +08:00
Hooray Hu 58182122dd chore: 更新依赖 2023-05-02 02:26:10 +08:00
Hooray Hu fb05790479 refactor: 入口 index.html 设置为不缓存 2023-04-26 23:03:52 +08:00
Hooray Hu c90138c718 chore: 更新依赖 2023-04-26 23:02:43 +08:00
Hooray Hu 54c3756a61 refactor: 修改 hidden 类名,避免和 unocss 产生冲突 2023-04-26 16:14:05 +08:00
Hooray Hu d6d7dadab7 fix: 修复主页无法引入请求 api 并使用的问题 2023-04-25 00:37:56 +08:00
Hooray Hu fab8f2cfdb chore: 更新依赖 2023-04-22 02:19:54 +08:00
Hooray Hu 11e68df11b chore!: 移除 BatchActionBar 组件 2023-04-22 01:24:21 +08:00
Hooray Hu 2109c194d6 refactor!: 重构接口请求,统一管理接口请求模块 2023-04-22 01:08:34 +08:00
Hooray Hu 247b5aa18b refactor!: 重构 SearchBar 组件 2023-04-19 16:37:12 +08:00
54 changed files with 3022 additions and 1524 deletions
+4
View File
@@ -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>
+27 -27
View File
@@ -1,5 +1,5 @@
{
"version": "2.11.0",
"version": "3.1.0",
"scripts": {
"dev": "vite",
"build:test": "vue-tsc --noEmit && vite build --mode test",
@@ -18,11 +18,11 @@
"release": "bumpp"
},
"dependencies": {
"@vueuse/core": "^9.13.0",
"@vueuse/integrations": "^9.13.0",
"axios": "^1.3.5",
"@vueuse/core": "^10.1.2",
"@vueuse/integrations": "^10.1.2",
"axios": "^1.4.0",
"dayjs": "^1.11.7",
"element-plus": "^2.3.3",
"element-plus": "^2.3.5",
"eruda": "^3.0.0",
"hotkeys-js": "^3.10.2",
"lodash-es": "^4.17.21",
@@ -31,56 +31,56 @@
"nprogress": "^0.2.0",
"path-browserify": "^1.0.1",
"path-to-regexp": "^6.2.1",
"pinia": "^2.0.34",
"qs": "^6.11.1",
"pinia": "^2.1.3",
"qs": "^6.11.2",
"vconsole": "^3.15.0",
"vue": "^3.2.47",
"vue-router": "^4.1.6"
"vue": "^3.3.4",
"vue-router": "^4.2.1"
},
"devDependencies": {
"@antfu/eslint-config": "^0.38.4",
"@antfu/eslint-config": "^0.39.3",
"@iconify-json/ep": "^1.1.10",
"@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.0.3",
"@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",
"eslint": "^8.41.0",
"esno": "^0.16.3",
"fs-extra": "^11.1.1",
"http-server": "^14.1.1",
"lint-staged": "^13.2.1",
"lint-staged": "^13.2.2",
"npm-run-all": "^4.1.5",
"plop": "^3.1.2",
"postcss-html": "^1.5.0",
"sass": "^1.62.0",
"sass": "^1.62.1",
"simple-git-hooks": "^2.8.1",
"stylelint": "^15.4.0",
"stylelint-config-standard": "^32.0.0",
"stylelint-config-standard-scss": "^8.0.0",
"stylelint": "^15.6.2",
"stylelint-config-standard": "^33.0.0",
"stylelint-config-standard-scss": "^9.0.0",
"stylelint-config-standard-vue": "^1.0.0",
"stylelint-scss": "^4.6.0",
"stylelint-stylistic": "^0.4.0",
"stylelint-scss": "^5.0.0",
"stylelint-stylistic": "^0.4.2",
"svgo": "^3.0.2",
"terser": "^5.16.9",
"terser": "^5.17.6",
"typescript": "^5.0.4",
"unplugin-auto-import": "^0.15.2",
"unplugin-auto-import": "^0.16.2",
"unplugin-vue-components": "^0.24.1",
"unplugin-vue-setup-extend-plus": "^1.0.0",
"vite": "^4.2.1",
"vite": "^4.3.8",
"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.30.1",
"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.4.2",
"vite-plugin-vue-meta-layouts": "^0.2.2",
"vue-tsc": "^1.2.0"
"vue-tsc": "^1.6.5"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged",
+6 -2
View File
@@ -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>
+5 -3
View File
@@ -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>
+2389 -884
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -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'
+2 -13
View File
@@ -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)
},
+13
View File
@@ -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/',
}),
}
+24
View File
@@ -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/',
}),
}
+5 -1
View File
@@ -1,8 +1,12 @@
<script lang="ts" setup name="Auth">
<script setup lang="ts">
const props = defineProps<{
value: string | string[]
}>()
defineOptions({
name: 'Auth',
})
function check() {
return useAuth().auth(props.value)
}
+5 -1
View File
@@ -1,8 +1,12 @@
<script lang="ts" setup name="AuthAll">
<script setup lang="ts">
const props = defineProps<{
value: string[]
}>()
defineOptions({
name: 'AuthAll',
})
function check() {
return useAuth().authAll(props.value)
}
-82
View File
@@ -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>
+5 -1
View File
@@ -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>
+35 -42
View File
@@ -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'],
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[],
]
}>()
defineOptions({
name: 'FileUpload',
})
const emit = defineEmits(['onSuccess'])
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>
+5 -1
View File
@@ -1,6 +1,10 @@
<script lang="ts" setup name="FixedActionBar">
<script setup lang="ts">
const isBottom = ref(false)
defineOptions({
name: 'FixedActionBar',
})
onMounted(() => {
onScroll()
window.addEventListener('scroll', onScroll)
+14 -13
View File
@@ -1,17 +1,18 @@
<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">
const props = withDefaults(
defineProps<{
src: string
width?: number | string
height?: number | string
}>(),
{
width: 200,
height: 200,
},
)
defineOptions({
name: 'ImagePreview',
})
const realWidth = computed(() => {
+40 -50
View File
@@ -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,
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'],
},
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'],
},
})
)
const emit = defineEmits(['update:url', 'onSuccess'])
const emits = defineEmits<{
'update:url': [
url: string,
]
'onSuccess': [
res: any,
]
}>()
defineOptions({
name: 'ImageUpload',
})
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>
+43 -55
View File
@@ -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,
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'],
},
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'],
},
})
)
const emit = defineEmits(['update:url', 'onSuccess'])
const emits = defineEmits<{
'update:url': [
value: string[],
]
'onSuccess': [
res: any,
]
}>()
defineOptions({
name: 'ImagesUpload',
})
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>
+5 -1
View File
@@ -1,4 +1,8 @@
<script lang="ts" setup>
<script setup lang="ts">
defineOptions({
name: 'NotAllowed',
})
const router = useRouter()
const data = ref({
+18 -24
View File
@@ -1,20 +1,23 @@
<script lang="ts" setup name="PageHeader">
defineProps({
title: {
type: String,
required: true,
},
content: {
type: String,
default: '',
<script setup lang="ts">
withDefaults(
defineProps<{
title: string
content?: string
}>(),
{
content: '',
},
)
defineOptions({
name: 'PageHeader',
})
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>
+15 -13
View File
@@ -1,17 +1,19 @@
<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">
const props = withDefaults(
defineProps<{
title?: string
collaspe?: boolean
height?: string
}>(),
{
title: '',
collaspe: false,
height: '',
},
)
defineOptions({
name: 'PageMain',
})
const titleSlot = !!useSlots().title
+29 -20
View File
@@ -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: () => [],
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',
},
disabled: {
type: Boolean,
default: false,
},
type: {
type: String as () => 'pc' | 'pca' | 'pcas',
default: 'pca',
},
format: {
type: String as () => 'code' | 'name' | 'both',
default: 'code',
},
})
)
const emit = defineEmits(['update:modelValue'])
const emits = defineEmits<{
'update:modelValue': [
value: string[] | {
code: string
name: string
}[],
]
}>()
defineOptions({
name: 'PcasCascader',
})
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
+40 -32
View File
@@ -1,40 +1,54 @@
<script lang="ts" setup name="SearchBar">
const props = defineProps({
showMore: {
type: Boolean,
default: false,
},
unfold: {
type: Boolean,
default: false,
<script setup lang="ts">
const props = withDefaults(
defineProps<{
fold?: boolean
showToggle?: boolean
background?: boolean
}>(),
{
fold: true,
showToggle: true,
background: false,
},
)
const emits = defineEmits<{
'update:fold': [
value: boolean,
]
'toggle': [
value: boolean,
]
}>()
defineOptions({
name: 'SearchBar',
})
const emit = defineEmits(['toggle'])
const isFold = ref(props.fold)
const isUnfold = ref(!props.unfold)
watch(() => props.unfold, () => toggle(), {
immediate: true,
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'" />
<svg-icon :name="isFold ? 'ep:caret-bottom' : 'ep:caret-top' " />
</el-icon>
</template>
{{ isUnfold ? '收起' : '展开' }}
{{ isFold ? '展开' : '收起' }}
</el-button>
</div>
</div>
@@ -43,17 +57,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 +76,7 @@ function toggle() {
}
}
.more {
.toggle {
position: relative;
text-align: center;
margin-bottom: -10px;
+14 -13
View File
@@ -1,19 +1,20 @@
<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,
const props = withDefaults(
defineProps<{
name: string
flip?: 'horizontal' | 'vertical' | 'both' | ''
rotate?: number
}>(),
{
flip: '',
rotate: 0,
},
)
defineOptions({
name: 'SvgIcon',
})
const transformStyle = computed(() => {
-3
View File
@@ -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>
+18 -21
View File
@@ -1,25 +1,22 @@
<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">
const props = withDefaults(
defineProps<{
value: string
type?: 'up' | 'down'
prefix?: string
suffix?: string
reverse?: boolean
}>(),
{
type: 'up',
prefix: '',
suffix: '',
reverse: false,
},
)
defineOptions({
name: 'Trend',
})
const isUp = computed(() => {
+5 -1
View File
@@ -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()
+5 -1
View File
@@ -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()
+13 -9
View File
@@ -1,16 +1,20 @@
<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,
withDefaults(
defineProps<{
showLogo?: boolean
showTitle?: boolean
}>(),
{
showLogo: true,
showTitle: true,
},
)
defineOptions({
name: 'Logo',
})
const settingsStore = useSettingsStore()
+5 -1
View File
@@ -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()
+7 -3
View File
@@ -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()
@@ -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)
+13 -11
View File
@@ -1,18 +1,20 @@
<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: '',
const props = withDefaults(
defineProps<{
item: Menu.recordRaw
basePath?: string
}>(),
{
basePath: '',
},
)
defineOptions({
name: 'SidebarItem',
})
const settingsStore = useSettingsStore()
@@ -50,7 +52,7 @@ const hasChildren = computed(() => {
</el-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>
+22 -13
View File
@@ -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>
+6 -6
View File
@@ -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
}
}
+5 -1
View File
@@ -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()
+5 -1
View File
@@ -1,4 +1,8 @@
<script lang="ts" setup name="LinkView">
<script setup lang="ts">
defineOptions({
name: 'LinkView',
})
const route = useRoute()
function open() {
+6 -2
View File
@@ -1,4 +1,4 @@
<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'
@@ -13,6 +13,10 @@ 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 +74,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 }">
+3
View File
@@ -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'
+9 -9
View File
@@ -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: '',
+1
View File
@@ -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'
+7 -8
View File
@@ -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
})
@@ -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 -4
View File
@@ -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(
@@ -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) {
+12 -14
View File
@@ -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
@@ -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 {
+1
View File
@@ -59,6 +59,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']
-2
View File
@@ -11,9 +11,7 @@ declare module '@vue/runtime-core' {
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']
+1 -1
View File
@@ -6,7 +6,7 @@ meta:
layout: false
</route>
<script lang="ts" setup>
<script setup lang="ts">
const router = useRouter()
const data = ref({
+107 -112
View File
@@ -4,7 +4,7 @@ meta:
title: 主页
</route>
<script lang="ts" setup>
<script setup lang="ts">
function open(url: string) {
window.open(url, '_blank')
}
@@ -86,110 +86,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://hooray.gitee.io/fantastic-admin/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://hooray.gitee.io/fantastic-admin/')">
访问官网
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="open('https://hooray.github.io/fantastic-admin/')">
备用地址
</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://hooray.gitee.io/one-step-admin/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://hooray.gitee.io/one-step-admin/')">
访问官网
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="open('https://hooray.github.io/one-step-admin/')">
备用地址
</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 +203,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 +260,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);
+5 -1
View File
@@ -5,11 +5,15 @@ 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 useUserStore from '@/store/modules/user'
defineOptions({
name: 'Login',
})
const route = useRoute()
const router = useRouter()
+5 -11
View File
@@ -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 -1
View File
@@ -5,10 +5,14 @@ meta:
cache: personal-edit.password
</route>
<script lang="ts" setup name="PersonalSetting">
<script setup lang="ts">
import type { UploadProps } from 'element-plus'
import { ElMessage } from 'element-plus'
defineOptions({
name: 'PersonalSetting',
})
const router = useRouter()
const form = ref({
+1 -1
View File
@@ -2,7 +2,7 @@
name: reload
</route>
<script lang="ts" setup>
<script setup lang="ts">
const router = useRouter()
onMounted(() => {
+8 -2
View File
@@ -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())
-5
View File
@@ -1,5 +0,0 @@
import setupExtend from 'unplugin-vue-setup-extend-plus/vite'
export default function createSetupExtend() {
return setupExtend({})
}