版本升级到v3:element-plus升级到v2、引入国际化、pinia

This commit is contained in:
huzhushan 2022-09-28 13:55:57 +08:00
parent 4b4b49011e
commit 7cc3274ebc
75 changed files with 3135 additions and 2865 deletions

View File

@ -24,7 +24,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 12:51:42
* @LastEditTime: 2022-09-24 16:29:19
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -71,5 +71,6 @@ module.exports = {
'vue/require-default-prop': 'off',
'vue/no-unused-components': 'warn',
'vue/no-setup-props-destructure': 'off',
'vue/script-setup-uses-vars': 'off',
},
}

View File

@ -24,7 +24,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-07-26 16:43:22
* @LastEditTime: 2022-09-27 18:51:22
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -51,10 +51,10 @@ export default [
name: 'testEdit',
title: '编辑',
},
{
name: 'testAuth',
title: '权限测试',
},
// {
// name: 'testAuth',
// title: '权限测试',
// },
{
name: 'test-cache',
title: '该页面可缓存',
@ -83,12 +83,6 @@ export default [
},
]
if (query.role === 'admin')
childs.push({
name: 'testNoAuth',
title: '权限页面',
})
return {
code: 200,
message: '获取菜单成功',

View File

@ -3,7 +3,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 09:39:02
* @LastEditTime: 2022-09-25 12:27:50
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -44,4 +44,28 @@ export default [
data: null,
},
},
// 请求用户列表
{
url: '/api/test/users',
method: 'post',
timeout: 1000,
response: () => {
// 响应内容
return {
code: 200,
message: '获取成功',
data: {
'list|10': [
{
'id|+1': 1,
nickName: '@cname()',
userEmail: '@email()',
'status|1': [0, 1],
},
],
'total|50-1000': 1,
},
}
},
},
]

3686
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "vue3-element-admin",
"version": "2.1.0",
"version": "3.0.0",
"author": {
"name": "huzhushan",
"email": "huzhushan@126.com",
@ -21,19 +21,21 @@
],
"dependencies": {
"axios": "^0.21.1",
"vue": "^3.0.5",
"vue": "3.2.33",
"vue-router": "^4.0.5",
"vuex": "^4.0.0"
"vuex": "^4.0.0",
"pinia": "^2.0.14"
},
"devDependencies": {
"@ehutch79/vite-eslint": "0.0.1",
"@vitejs/plugin-vue": "^1.1.5",
"@vue/compiler-sfc": "^3.0.5",
"@vitejs/plugin-vue": "^1.2.3",
"@vue/compiler-sfc": "^3.1.2",
"@vue/eslint-config-prettier": "^6.0.0",
"autoprefixer": "^10.2.5",
"babel-eslint": "^10.1.0",
"crypto-js": "^4.0.0",
"element-plus": "^1.0.2-beta.71",
"element-plus": "^2.2.13",
"vue-i18n": "^9.0.0",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-vue": "^7.0.0-0",
@ -42,7 +44,7 @@
"mockjs": "^1.1.0",
"prettier": "^1.19.1",
"sass": "^1.41.1",
"vite": "^2.1.0",
"vite": "^2.3.7",
"vite-plugin-mock": "^2.3.0",
"vite-plugin-svg-icons": "^0.4.0"
},

View File

@ -24,15 +24,14 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 13:17:44
* @LastEditTime: 2022-09-26 12:14:10
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
-->
<template>
<el-config-provider :locale="locale">
<el-config-provider :locale="locales[lang]">
<router-view />
</el-config-provider>
</template>
@ -40,16 +39,22 @@
<script>
import { defineComponent } from 'vue'
import { ElConfigProvider } from 'element-plus'
import zhCn from 'element-plus/lib/locale/lang/zh-cn'
import localeZH from 'element-plus/lib/locale/lang/zh-cn'
import localeEN from 'element-plus/lib/locale/lang/en'
import useLang from '@/i18n/useLang'
export default defineComponent({
components: {
[ElConfigProvider.name]: ElConfigProvider,
},
data() {
setup() {
const { lang } = useLang()
return {
locale: zhCn,
lang,
locales: {
'zh-cn': localeZH,
en: localeEN,
},
}
},
})
@ -63,5 +68,8 @@ body,
height: 100%;
margin: 0;
padding: 0;
* {
outline: none;
}
}
</style>

View File

@ -3,7 +3,7 @@
* @version:
* @Date: 2021-04-20 16:35:04
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 09:37:00
* @LastEditTime: 2022-09-25 11:50:39
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -18,3 +18,12 @@ export const TestError = () => {
method: 'get',
})
}
// 用户列表
export const getUsers = data => {
return request({
url: '/api/test/users',
method: 'post',
data,
})
}

View File

@ -1,11 +1,14 @@
/* 改变主题色变量 */
$--colors: (
'primary': (
'base': $mainColor,
/**如果需要修改其它变量可以在以下文件中查找
* https://github.com/element-plus/element-plus/blob/dev/packages/theme-chalk/src/common/var.scss
*/
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
$colors: (
'primary': (
'base': $mainColor,
),
),
);
/* 改变 icon 字体路径变量,必需 */
$--font-path: 'element-plus/lib/theme-chalk/fonts';
@import 'element-plus/packages/theme-chalk/src/reset';
@import 'element-plus/packages/theme-chalk/src/index';
@use "element-plus/theme-chalk/src/reset.scss" as *;
@use "element-plus/theme-chalk/src/index.scss" as *;

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M510.72 41.856l435.2 361.024a32 32 0 0 1-36.16 52.48l-4.736-3.2-41.024-34.048V832c0 48.832-32.64 90.752-76.864 95.552l-8.448 0.448H245.312c-45.696 0-80.96-39.04-84.928-86.912L160 832V407.808l-55.04 44.608a32 32 0 0 1-40.96-0.64l-4.032-4.16a32 32 0 0 1 0.64-40.96l4.096-4.032L510.784 41.856z m-0.512 82.688L223.232 356.672a31.808 31.808 0 0 1 0.256 1.152l0.512 5.76V832c0 16.768 8.64 28.992 17.92 31.552l3.392 0.448h533.376c9.216 0 18.88-10.432 20.928-25.92L800 832V364.992L510.208 124.544z" /></svg>

After

Width:  |  Height:  |  Size: 766 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1664017268288" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2638" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M848.805886 805.572222c70.998007-81.260745 109.779266-184.217628 109.779266-293.14448 0-119.204939-46.421262-231.277434-130.713041-315.569212C744.876861 113.862257 634.94103 67.61598 517.788843 66.213028c-1.924839-0.599657-10.290367-0.592494-12.227486 0.01535C388.878868 67.945485 279.434224 114.159016 196.73471 196.85853 113.863281 279.730982 67.630307 389.460106 66.095347 506.415818c-0.428765 1.64957-0.436952 8.601912-0.021489 10.226922 1.082658 117.628024 47.364751 228.058113 130.660852 311.354214 84.291778 84.291778 196.36325 130.713041 315.569212 130.713041 119.204939 0 231.277434-46.421262 315.569212-130.713041 6.139837-6.139837 12.054547-12.444427 17.789155-18.871813 0.50756-0.453325 1.001817-0.928139 1.471514-1.440815C847.750857 807.012014 848.295256 806.299793 848.805886 805.572222zM107.447151 532.043499l187.501418 0c1.322112 65.678862 9.253758 127.264499 22.505573 182.112688-61.690014 16.687054-100.819197 38.371936-121.076566 51.906184C144.30971 701.336206 111.676475 620.35687 107.447151 532.043499zM195.881272 259.408121c20.090571 13.556761 59.242266 35.461653 121.340579 52.260248-12.998035 54.127781-20.827351 114.778116-22.243607 179.432649L107.525945 491.101018C112.076588 403.731134 144.437623 323.612399 195.881272 259.408121zM917.081898 491.099994 729.628576 491.099994c-1.415232-64.630996-9.240455-125.260865-22.229281-179.37432 61.95505-16.693194 101.235682-38.444591 121.56673-52.020794C880.270505 323.860039 912.537396 403.866211 917.081898 491.099994zM688.677908 491.099994 532.167319 491.099994 532.167319 335.061149c52.209082-1.094938 97.103572-6.453992 135.272893-14.033621C680.000272 373.163955 687.286212 430.896844 688.677908 491.099994zM532.167319 294.115598 532.167319 109.918435c36.84107 10.398838 72.779583 49.205679 100.926644 110.015649 8.810666 19.035542 16.645099 39.641859 23.464411 61.521169C621.531626 288.227494 580.261687 293.062616 532.167319 294.115598zM491.223814 110.273523l0 183.805236c-47.504944-1.12666-88.378863-6.001691-123.120109-12.802584 6.807033-21.812795 14.623046-42.35976 23.409153-61.344137C419.351903 159.792333 454.809463 121.175827 491.223814 110.273523zM491.223814 335.040682l0 156.059312L335.928912 491.099994c1.391696-60.213383 8.679683-117.955482 21.243837-170.099073C395.008472 328.536548 439.487499 333.887416 491.223814 335.040682zM335.893096 532.043499l155.330718 0 0 158.667719c-51.609425 1.194198-96.019891 6.563486-133.821845 14.103206C344.576873 651.927913 337.193719 593.243349 335.893096 532.043499zM491.223814 731.672118l0 182.909843c-36.415374-10.902304-71.871911-49.51881-99.709933-109.659539-8.679683-18.752086-16.409738-39.034015-23.157419-60.551074C402.9964 737.645157 443.773106 732.820268 491.223814 731.672118zM532.167319 914.937049 532.167319 731.608673c47.904033 1.025353 89.103364 5.862521 124.116809 12.656251-6.755868 21.555945-14.497179 41.87369-23.190165 60.656475C604.946902 865.73137 569.008388 904.538211 532.167319 914.937049zM532.167319 690.660052 532.167319 532.043499l156.546406 0c-1.298576 61.096497-8.66024 119.68487-21.445428 172.502819C629.154233 697.013761 584.319096 691.710988 532.167319 690.660052zM729.659275 532.043499l187.501418 0c-4.221138 88.138386-36.732599 168.973436-88.620363 233.635131-20.469194-13.668301-59.635215-35.298947-121.30374-51.868321C720.43724 659.049101 728.33921 597.585237 729.659275 532.043499zM801.518906 228.742704c-18.329461 11.570523-52.309366 29.355585-104.858186 43.493583-19.295462-63.056128-46.110177-115.004267-78.06189-150.97655C689.00025 140.410913 751.833297 178.097234 801.518906 228.742704zM406.007991 121.259738c-31.905664 35.920094-58.690704 87.768973-77.979002 150.702304-52.40351-14.241352-86.370113-32.099069-104.581893-43.587728C273.076422 177.914062 335.777463 140.364865 406.007991 121.259738zM223.917816 796.963147c18.284435-11.535731 52.098565-29.230742 104.332207-43.335994 19.271926 62.60485 45.976124 114.186645 77.757968 149.968593C335.99952 884.550994 273.472442 847.181899 223.917816 796.963147zM618.59883 903.595746c31.801287-35.803437 58.517765-87.426165 77.792761-150.08218 51.984978 14.023388 85.972047 31.631418 104.533798 43.208081C751.3329 847.061149 688.718841 884.521319 618.59883 903.595746z" p-id="2639"></path></svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -17,19 +17,16 @@
* @version:
* @Date: 2021-04-23 14:56:06
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-23 15:00:31
* @LastEditTime: 2022-09-27 16:07:53
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
import { computed } from 'vue'
import { useStore } from 'vuex'
import { storeToRefs } from 'pinia'
import { useAccount } from '@/pinia/modules/account'
export const useUserinfo = () => {
const store = useStore()
const userinfo = computed(() => store.state.account.userinfo)
const { userinfo } = storeToRefs(useAccount())
return { userinfo }
}

View File

@ -37,7 +37,7 @@
* @version:
* @Date: 2021-04-21 09:18:32
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 12:45:38
* @LastEditTime: 2022-09-27 17:50:59
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -111,18 +111,20 @@
</template>
<script>
import { defineComponent, ref, computed } from 'vue'
import { useStore } from 'vuex'
import { defineComponent, ref } from 'vue'
import { useErrorlog } from '@/pinia/modules/errorLog'
import { storeToRefs } from 'pinia'
export default defineComponent({
name: 'ErrorLog',
setup() {
const dialogTableVisible = ref(false)
const store = useStore()
const errorLogs = computed(() => store.state.errorLog.logs)
const errorStore = useErrorlog()
const { logs: errorLogs } = storeToRefs(errorStore)
const { clearErrorLog } = errorStore
const clearAll = () => {
dialogTableVisible.value = false
store.dispatch('errorLog/clearErrorLog')
clearErrorLog()
}
return {

View File

@ -22,7 +22,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-29 15:04:03
* @LastEditTime: 2022-09-25 11:53:47
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -45,7 +45,7 @@
<el-form-item
v-for="item in search.fields"
:key="item.name"
:label="item.label"
:label="$t(item.label)"
:prop="item.name"
>
<slot v-if="item.type === 'custom'" :name="item.slot" />
@ -55,13 +55,13 @@
:filterable="!!item.filterable"
:multiple="!!item.multiple"
clearable
:placeholder="`请选择${item.label}`"
:placeholder="$t(item.label)"
:style="{ width: search.inputWidth, ...item.style }"
>
<el-option
v-for="option of item.options"
:key="option.value"
:label="option.name"
:label="$t(option.name)"
:value="option.value"
></el-option>
</el-select>
@ -75,7 +75,7 @@
:key="option.value"
:label="option.value"
>
{{ option.name }}
{{ $t(option.name) }}
</el-radio>
</el-radio-group>
<el-radio-group
@ -88,7 +88,7 @@
:key="option.value"
:label="option.value"
>
{{ option.name }}
{{ $t(option.name) }}
</el-radio-button>
</el-radio-group>
<el-checkbox-group
@ -101,7 +101,7 @@
:key="option.value"
:label="option.value"
>
{{ option.name }}
{{ $t(option.name) }}
</el-checkbox>
</el-checkbox-group>
<el-checkbox-group
@ -114,7 +114,7 @@
:key="option.value"
:label="option.value"
>
{{ option.name }}
{{ $t(option.name) }}
</el-checkbox-button>
</el-checkbox-group>
<el-date-picker
@ -124,7 +124,7 @@
format="YYYY-MM-DD"
clearable
@change="handleDateChange($event, item, 'YYYY-MM-DD')"
:placeholder="`请选择${item.label}`"
:placeholder="$t(item.label)"
:style="{ width: search.inputWidth, ...item.style }"
></el-date-picker>
<el-date-picker
@ -134,7 +134,7 @@
clearable
@change="handleDateChange($event, item, 'YYYY-MM-DD HH:mm:ss')"
format="YYYY-MM-DD HH:mm:ss"
:placeholder="`请选择${item.label}`"
:placeholder="$t(item.label)"
:style="{ width: search.inputWidth, ...item.style }"
></el-date-picker>
<el-date-picker
@ -143,11 +143,11 @@
type="daterange"
format="YYYY-MM-DD"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:start-placeholder="$t('public.startdate')"
:end-placeholder="$t('public.enddate')"
clearable
@change="handleRangeChange($event, item, 'YYYY-MM-DD')"
:style="{ width: search.inputWidth, ...item.style }"
:style="{ ...item.style }"
></el-date-picker>
<el-date-picker
v-else-if="item.type === 'datetimerange'"
@ -155,16 +155,16 @@
type="datetimerange"
format="YYYY-MM-DD HH:mm:ss"
range-separator="-"
start-placeholder="开始时间"
end-placeholder="结束时间"
:start-placeholder="$t('public.starttime')"
:end-placeholder="$t('public.endtime')"
clearable
@change="handleRangeChange($event, item, 'YYYY-MM-DD HH:mm:ss')"
:style="{ width: search.inputWidth, ...item.style }"
:style="{ ...item.style }"
></el-date-picker>
<el-input-number
v-else-if="item.type === 'number'"
v-model="searchModel[item.name]"
:placeholder="`请输入${item.label}`"
:placeholder="$t(item.label)"
controls-position="right"
:min="item.min"
:max="item.max"
@ -176,7 +176,7 @@
type="textarea"
clearable
v-model="searchModel[item.name]"
:placeholder="`请输入${item.label}`"
:placeholder="$t(item.label)"
:style="{ width: search.inputWidth, ...item.style }"
></el-input>
<el-input
@ -184,16 +184,16 @@
:maxlength="item.maxlength"
v-model="searchModel[item.name]"
clearable
:placeholder="`请输入${item.label}`"
:placeholder="$t(item.label)"
:style="{ width: search.inputWidth, ...item.style }"
></el-input>
</el-form-item>
<el-form-item class="search-btn">
<el-button type="primary" icon="el-icon-search" @click="handleSearch">
查询
<el-button type="primary" icon="Search" @click="handleSearch">
{{ $t('public.search') }}
</el-button>
<el-button @click="handleReset" icon="el-icon-refresh-right">
重置
<el-button @click="handleReset" icon="RefreshRight">
{{ $t('public.reset') }}
</el-button>
</el-form-item>
</el-form>
@ -224,6 +224,7 @@
:filter-method="item.filters && filterHandler"
:show-overflow-tooltip="!item.wrap"
v-bind="item"
:label="item.label ? $t(item.label) : ''"
>
<template #header="scope" v-if="!!item.labelSlot">
<slot :name="item.labelSlot" v-bind="scope"></slot>
@ -511,6 +512,9 @@ export default defineComponent({
:deep(.el-input-number .el-input__inner) {
text-align: left;
}
:deep(.el-range-editor.el-input__wrapper) {
box-sizing: border-box;
}
}
.head {
@ -530,7 +534,7 @@ export default defineComponent({
.pagination {
padding: 0 20px 20px;
background: #fff;
text-align: right;
justify-content: flex-end;
:last-child {
margin-right: 0;
}

View File

@ -27,19 +27,19 @@
* @version:
* @Date: 2021-09-01 13:58:08
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-29 10:10:32
* @LastEditTime: 2022-09-27 18:31:22
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
import store from '@/store'
import { useAccount } from '@/pinia/modules/account'
export const Permission = app => {
app.directive('permission', {
mounted: function(el, binding) {
const permissionList = store.state.account.permissionList || []
const { permissionList } = useAccount()
if (
binding.value &&

View File

@ -13,7 +13,7 @@
* @version:
* @Date: 2021-04-21 09:18:32
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 12:48:49
* @LastEditTime: 2022-09-27 15:53:02
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -21,7 +21,8 @@
*/
import { nextTick } from 'vue'
import store from '@/store'
import { useErrorlog } from './pinia/modules/errorLog'
// import store from '@/store'
// 判断环境,决定是否开启错误监控
// - import.meta.env.DEV 布尔值,代表开发环境
@ -34,7 +35,7 @@ export default app => {
if (flag) {
app.config.errorHandler = function(err, vm, info) {
nextTick(() => {
store.dispatch('errorLog/addErrorLog', {
useErrorlog().addErrorLog({
err,
// vm, // 这里不保存vm否则渲染错误日志的时候控制台会有警告
info,

View File

@ -14,7 +14,7 @@
* @version:
* @Date: 2021-09-18 09:32:01
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 17:54:29
* @LastEditTime: 2022-09-24 14:43:31
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -23,4 +23,3 @@
export { default as SvgIcon } from '@/components/SvgIcon/index.vue'
export { default as ProTable } from '@/components/ProTable/index.vue'
export { default as ElSelectTree } from '@/components/SelectTree/index.vue'

View File

@ -27,23 +27,23 @@
* @version:
* @Date: 2021-08-20 11:15:27
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 17:25:32
* @LastEditTime: 2022-09-27 16:15:56
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
import { useTags } from '@/pinia/modules/tags'
import { reactive, toRefs, getCurrentInstance } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useStore } from 'vuex'
// 关闭当前标签
export default () => {
const instance = getCurrentInstance()
const store = useStore()
const router = useRouter()
const route = useRoute()
const { delTag } = useTags()
const state = reactive({
/**
* @param {String} fullPath 要跳转到那个页面的地址
@ -52,7 +52,7 @@ export default () => {
* @return {*}
*/
closeTag({ fullPath, reload, f5 } = {}) {
store.dispatch('tags/delTag', route)
delTag(route)
fullPath ? router.push(fullPath) : router.back()
reload &&
setTimeout(() => {

19
src/i18n/index.js Normal file
View File

@ -0,0 +1,19 @@
import { createI18n } from 'vue-i18n'
const getMessage = modules => {
return Object.entries(modules).reduce((module, [path, mod]) => {
const moduleName = path.replace(/^\.\/locales\/[\w-]+\/(.*)\.\w+$/, '$1')
module[moduleName] = mod.default
return module
}, {})
}
export default createI18n({
locale: localStorage.getItem('__VEA__lang') || 'zh-cn',
messages: {
'zh-cn': getMessage(import.meta.globEager('./locales/zh-cn/**/*.js')),
en: getMessage(import.meta.globEager('./locales/en/**/*.js')),
},
legacy: false,
globalInjection: true,
})

View File

@ -0,0 +1,6 @@
export default {
noauth: 'No access',
servererror: 'Server error',
notfound: 'Page not found',
backhome: 'Back to Home',
}

View File

@ -0,0 +1,10 @@
export default {
username: 'Username',
password: 'Password',
login: 'Login',
logining: 'Login...',
loginsuccess: 'Success',
'rules-username': 'Please input username',
'rules-password': 'Please input password',
'rules-regpassword': '6 to 12 characters in length',
}

View File

@ -0,0 +1,16 @@
export default {
homepage: 'Homepage',
dashboard: 'Dashboard',
test: 'Test page',
testList: 'List',
testAdd: 'Add',
testEdit: 'Edit',
testAuth: 'Auth',
testNoAuth: 'No auth',
'test-cache': 'Cache',
'test-no-cache': 'No Cache',
nest: 'Nest page',
nestPage1: 'Page1',
nestPage2: 'Page2',
'test-error-log': 'Error log',
}

View File

@ -0,0 +1,22 @@
export default {
sure: 'Sure',
search: 'Search',
reset: 'Reset',
edit: 'Edit',
add: 'Add',
delete: 'Delete',
save: 'Save',
cancel: 'Cancel',
yes: 'Yes',
no: 'No',
status: 'Status',
operate: 'Operate',
enabled: 'Enabled',
disabled: 'Disabled',
male: 'Male',
female: 'Female',
startdate: 'From',
enddate: 'To',
starttime: 'From',
endtime: 'To',
}

View File

@ -0,0 +1,8 @@
export default {
refresh: 'Refresh',
close: 'Close',
other: 'Close other',
left: 'Close left',
right: 'Close right',
all: 'Close all',
}

View File

@ -0,0 +1,32 @@
export default {
title: 'List',
batchDelete: 'Batch delete',
add: 'Add one',
refresh: 'Refresh',
index: 'Index',
name: 'Nickname',
email: 'Email',
desc: 'Description',
publish: 'Published',
nopublish: 'Unpublished',
gender: 'Gender',
city: 'City',
bj: 'Beijing',
sh: 'Shanghai',
gz: 'Guangzhou',
sz: 'Shenzhen',
hobby: 'Hobby',
eat: 'Eat',
sleep: 'Sleep',
bit: 'Beat',
fruit: 'Fruit',
apple: 'Apple',
banana: 'Banana',
orange: 'Orange',
grape: 'Grape',
date: 'Date',
daterange: 'Date range',
time: 'Time',
timerange: 'Time range',
num: 'Number',
}

View File

@ -0,0 +1,21 @@
export default {
center: 'User center',
password: 'Modify password',
logout: 'Logout',
'lock-title': 'Lock screen',
'lock-password': 'Password',
'lock-rules-password': 'Please input Screen password',
'lock-locked': 'Screen Locked',
'lock-lock': 'Unlock',
'lock-relogin': 'Re-login',
'lock-rules-password2': 'Screen password or User password',
'lock-rules-password3': 'Password error',
'lock-error': 'Your account has been logged out, please log in directly',
'lock-week0': 'Sunday',
'lock-week1': 'Monday',
'lock-week2': 'Tuesday',
'lock-week3': 'Wednesday',
'lock-week4': 'Thursday',
'lock-week5': 'Friday',
'lock-week6': 'Saturday',
}

View File

@ -0,0 +1,6 @@
export default {
noauth: '您无权访问此页面',
servererror: '服务器出错了',
notfound: '您访问的页面不存在',
backhome: '返回首页',
}

View File

@ -0,0 +1,10 @@
export default {
username: '用户名',
password: '密码',
login: '登录',
logining: '登录中...',
loginsuccess: '登录成功',
'rules-username': '请输入用户名',
'rules-password': '请输入密码',
'rules-regpassword': '长度在 6 到 12 个字符',
}

View File

@ -0,0 +1,16 @@
export default {
homepage: '首页',
dashboard: '工作台',
test: '测试页面',
testList: '列表',
testAdd: '添加',
testEdit: '编辑',
testAuth: '权限测试',
testNoAuth: '权限页面',
'test-cache': '该页面可缓存',
'test-no-cache': '该页面不缓存',
nest: '二级页面',
nestPage1: 'Page1',
nestPage2: 'Page2',
'test-error-log': '测试错误日志',
}

View File

@ -0,0 +1,22 @@
export default {
sure: '确定',
search: '搜索',
reset: '重置',
edit: '编辑',
add: '新增',
delete: '删除',
save: '保存',
cancel: '取消',
yes: '是',
no: '否',
status: '状态',
operate: '操作',
enabled: '启用',
disabled: '禁用',
male: '男',
female: '女',
startdate: '开始日期',
enddate: '结束日期',
starttime: '开始时间',
endtime: '结束时间',
}

View File

@ -0,0 +1,8 @@
export default {
refresh: '刷新',
close: '关闭',
other: '关闭其它',
left: '关闭左侧',
right: '关闭右侧',
all: '关闭全部',
}

View File

@ -0,0 +1,32 @@
export default {
title: '列表',
batchDelete: '批量删除',
add: '添加一条',
refresh: '刷新',
index: '序号',
name: '昵称',
email: '邮箱',
desc: '描述',
publish: '已发布',
nopublish: '未发布',
gender: '性别',
city: '城市',
bj: '北京',
sh: '上海',
gz: '广州',
sz: '深圳',
hobby: '爱好',
eat: '吃饭',
sleep: '睡觉',
bit: '打豆豆',
fruit: '水果',
apple: '苹果',
banana: '香蕉',
orange: '橘子',
grape: '葡萄',
date: '日期',
daterange: '日期范围',
time: '时间',
timerange: '时间范围',
num: '数量',
}

View File

@ -0,0 +1,21 @@
export default {
center: '个人中心',
password: '修改密码',
logout: '退出登录',
'lock-title': '锁定屏幕',
'lock-password': '锁屏密码',
'lock-rules-password': '请输入锁屏密码',
'lock-locked': '屏幕已锁定',
'lock-lock': '解锁',
'lock-relogin': '重新登录',
'lock-rules-password2': '请输入锁屏密码或登录密码',
'lock-rules-password3': '密码错误',
'lock-error': '您的账号已退出,请直接登录',
'lock-week0': '星期日',
'lock-week1': '星期一',
'lock-week2': '星期二',
'lock-week3': '星期三',
'lock-week4': '星期四',
'lock-week5': '星期五',
'lock-week6': '星期六',
}

16
src/i18n/useLang.js Normal file
View File

@ -0,0 +1,16 @@
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
export default function useLang() {
const i18n = useI18n()
const lang = computed(() => i18n.locale.value)
const changeLang = value => {
i18n.locale.value = value
localStorage.setItem('__VEA__lang', value)
}
return {
i18n,
lang,
changeLang,
}
}

View File

@ -27,7 +27,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 12:46:17
* @LastEditTime: 2022-09-27 16:27:54
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -42,15 +42,15 @@
</router-view>
</template>
<script>
import { storeToRefs } from 'pinia'
import { computed, defineComponent } from 'vue'
import { useRoute } from 'vue-router'
import { useStore } from 'vuex'
import { useTags } from '@/pinia/modules/tags'
export default defineComponent({
setup() {
const store = useStore()
const route = useRoute()
const cacheList = computed(() => store.state.tags.cacheList)
const { cacheList } = storeToRefs(useTags())
const key = computed(() => route.fullPath)
return {

View File

@ -24,7 +24,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 12:46:37
* @LastEditTime: 2022-09-24 19:33:12
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -32,9 +32,9 @@
-->
<template>
<i v-if="isElementIcon" :class="`icon ${icon}`" />
<svg-icon class="icon" v-else-if="!!icon" :name="icon" />
<span>{{ title }}</span>
<svg-icon class="icon" v-if="isCustomSvg" :name="icon" />
<component :is="icon" v-else-if="!!icon" class="icon" />
<span>{{ $t(title) }}</span>
</template>
<script>
@ -43,10 +43,10 @@ import { computed, defineComponent } from 'vue'
export default defineComponent({
props: ['title', 'icon'],
setup({ icon }) {
const isElementIcon = computed(() => icon && icon.startsWith('el-icon'))
const isCustomSvg = computed(() => icon && icon.startsWith('icon-'))
return {
isElementIcon,
isCustomSvg,
}
},
})

View File

@ -27,7 +27,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-07-26 16:02:28
* @LastEditTime: 2022-09-27 16:45:42
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -54,9 +54,10 @@
<script>
import { computed, defineComponent } from 'vue'
import Submenu from './Submenu.vue'
import { useStore } from 'vuex'
import { useRoute } from 'vue-router'
import config from './config/menu.module.scss'
import { storeToRefs } from 'pinia'
import { useMenus } from '@/pinia/modules/menu'
export default defineComponent({
components: {
@ -74,10 +75,10 @@ export default defineComponent({
},
setup() {
const route = useRoute()
const store = useStore()
const { menus } = storeToRefs(useMenus())
return {
menus: computed(() => store.state.menu.menus),
menus,
activePath: computed(() => route.path),
variables: computed(() => config),
}
@ -87,15 +88,15 @@ export default defineComponent({
<style lang="scss">
// menu hover
.el-menu-item,
.el-submenu__title {
.el-sub-menu__title {
&:hover {
background-color: $menuHover !important;
}
}
.el-submenu {
.el-sub-menu {
.el-menu-item,
.el-submenu .el-submenu__title {
.el-sub-menu .el-sub-menu__title {
background-color: $subMenuBg !important;
&:hover {
@ -112,7 +113,7 @@ export default defineComponent({
.el-menu--collapse {
.el-menu-item.is-active,
.el-submenu.is-active > .el-submenu__title {
.el-sub-menu.is-active > .el-sub-menu__title {
position: relative;
background-color: $collapseMenuActiveBg !important;
color: $collapseMenuActiveColor !important;
@ -128,19 +129,20 @@ export default defineComponent({
}
}
.el-submenu__title i {
.el-sub-menu__title i {
color: $arrowColor;
}
//
.el-menu--horizontal {
.el-menu-item,
.el-submenu .el-submenu__title {
.el-sub-menu .el-sub-menu__title {
height: $horizontalMenuHeight;
line-height: $horizontalMenuHeight;
border-bottom: none;
}
.el-menu-item.is-active,
.el-submenu.is-active .el-submenu__title {
.el-sub-menu.is-active .el-sub-menu__title {
border: none;
}
}

View File

@ -13,7 +13,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 12:46:55
* @LastEditTime: 2022-09-24 16:44:28
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -24,7 +24,7 @@
<el-menu-item v-if="!menu.children" :index="menu.url">
<item :icon="menu.icon" :title="menu.title" />
</el-menu-item>
<el-submenu v-else :index="menu.url">
<el-sub-menu v-else :index="menu.url">
<template #title>
<item :icon="menu.icon" :title="menu.title" />
</template>
@ -34,7 +34,7 @@
:is-nest="true"
:menu="submenu"
/>
</el-submenu>
</el-sub-menu>
</template>
<script>
import { defineComponent } from 'vue'

View File

@ -25,7 +25,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-07-23 09:36:42
* @LastEditTime: 2022-09-27 18:45:07
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -44,10 +44,11 @@
</template>
<script>
import { defineComponent, computed } from 'vue'
import { useApp } from '@/pinia/modules/app'
import { storeToRefs } from 'pinia'
import { computed, defineComponent } from 'vue'
import Logo from './Logo.vue'
import Menus from './Menus.vue'
import { useStore } from 'vuex'
export default defineComponent({
components: {
@ -55,12 +56,13 @@ export default defineComponent({
Menus,
},
setup() {
const store = useStore()
const collapse = computed(() => !!store.state.app.sidebar.collapse)
const device = computed(() => store.state.app.device)
const appStore = useApp()
const { sidebar, device } = storeToRefs(appStore)
const { setCollapse } = appStore
const collapse = computed(() => sidebar.value.collapse)
const closeSidebar = () => {
store.commit('app/setCollapse', 1)
setCollapse(1)
}
return {

View File

@ -26,23 +26,24 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-29 10:52:41
* @LastEditTime: 2022-09-27 16:52:30
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
import { useTags } from '@/pinia/modules/tags'
import { onMounted, onBeforeUnmount, reactive, toRefs, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { isAffix } from './useTags'
export const useContextMenu = tagList => {
const store = useStore()
const router = useRouter()
const route = useRoute()
const tagsStore = useTags()
const state = reactive({
visible: false,
top: 0,
@ -58,7 +59,7 @@ export const useContextMenu = tagList => {
state.visible = false
},
refreshSelectedTag(tag) {
store.commit('tags/DEL_CACHE_LIST', tag)
tagsStore.deCacheList(tag)
const { fullPath } = tag
nextTick(() => {
router.replace({
@ -72,13 +73,13 @@ export const useContextMenu = tagList => {
const closedTagIndex = tagList.value.findIndex(
item => item.fullPath === tag.fullPath
)
store.dispatch('tags/delTag', tag)
tagsStore.delTag(tag)
if (isActive(tag)) {
toLastTag(closedTagIndex - 1)
}
},
closeOtherTags() {
store.dispatch('tags/delOtherTags', state.selectedTag)
tagsStore.delOtherTags(state.selectedTag)
router.push(state.selectedTag)
},
closeLeftTags() {
@ -103,11 +104,11 @@ export const useContextMenu = tagList => {
direction === 'left'
? tagList.value.slice(0, index)
: tagList.value.slice(index + 1)
store.dispatch('tags/delSomeTags', needToClose)
tagsStore.delSomeTags(needToClose)
router.push(state.selectedTag)
},
closeAllTags() {
store.dispatch('tags/delAllTags')
tagsStore.delAllTags()
router.push('/')
},
})

View File

@ -24,7 +24,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 12:47:07
* @LastEditTime: 2022-08-13 14:50:23
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -35,15 +35,28 @@ import { ref } from 'vue'
export const useScrollbar = tagsItem => {
const scrollContainer = ref(null)
const scrollLeft = ref(0)
const doScroll = val => {
scrollLeft.value = val
scrollContainer.value.setScrollLeft(scrollLeft.value)
}
const handleScroll = e => {
const $wrap = scrollContainer.value.wrap$
if ($wrap.offsetWidth + scrollLeft.value > $wrap.children[0].scrollWidth) {
doScroll($wrap.children[0].scrollWidth - $wrap.offsetWidth)
return
} else if (scrollLeft.value < 0) {
doScroll(0)
return
}
const eventDelta = e.wheelDelta || -e.deltaY
scrollContainer.value.wrap.scrollLeft -= eventDelta / 4
doScroll(scrollLeft.value - eventDelta / 4)
}
const moveToTarget = currentTag => {
const containerWidth = scrollContainer.value.scrollbar.offsetWidth
const scrollWrapper = scrollContainer.value.wrap
const $wrap = scrollContainer.value.wrap$
const tagList = tagsItem.value
let firstTag = null
@ -54,15 +67,15 @@ export const useScrollbar = tagsItem => {
lastTag = tagList[tagList.length - 1]
}
if (firstTag === currentTag) {
scrollWrapper.scrollLeft = 0
doScroll(0)
} else if (lastTag === currentTag) {
scrollWrapper.scrollLeft = scrollWrapper.scrollWidth - containerWidth
doScroll($wrap.children[0].scrollWidth - $wrap.offsetWidth)
} else {
const el = currentTag.$el.nextElementSibling
scrollWrapper.scrollLeft =
el.offsetLeft + el.offsetWidth > containerWidth
? el.offsetLeft - el.offsetWidth
: 0
el.offsetLeft + el.offsetWidth > $wrap.offsetWidth
? doScroll(el.offsetLeft - el.offsetWidth)
: doScroll(0)
}
}

View File

@ -24,28 +24,29 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-11-23 10:56:09
* @LastEditTime: 2022-09-27 18:28:33
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
import { storeToRefs } from 'pinia'
import { useTags as useTagsbar } from '@/pinia/modules/tags'
import { useScrollbar } from './useScrollbar'
import { watch, computed, ref, nextTick, onBeforeMount } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
export const isAffix = tag => {
return !!tag.meta && !!tag.meta.affix
}
export const useTags = () => {
const store = useStore()
const tagStore = useTagsbar()
const { tagList } = storeToRefs(tagStore)
const { addTag, delTag, saveActivePosition, updateTagList } = tagStore
const router = useRouter()
const route = router.currentRoute
const routes = computed(() => router.getRoutes())
const tagList = computed(() => store.state.tags.tagList)
const tagsItem = ref([])
@ -71,32 +72,31 @@ export const useTags = () => {
for (const tag of affixTags) {
if (tag.name) {
store.dispatch('tags/addTag', tag)
addTag(tag)
}
}
// 不在路由中的所有标签,需要删除
const noUseTags = tagList.value.filter(tag =>
routes.value.every(route => route.name !== tag.name)
)
noUseTags.forEach(tag => {
store.dispatch('tags/delTag', tag)
delTag(tag)
})
}
const addTag = () => {
const addTagList = () => {
const tag = route.value
if (!!tag.name && tag.matched[0].components.default.name === 'layout') {
store.dispatch('tags/addTag', tag)
addTag(tag)
}
}
const saveActivePosition = tag => {
const saveTagPosition = tag => {
const index = tagList.value.findIndex(
item => item.fullPath === tag.fullPath
)
store.dispatch('tags/saveActivePosition', Math.max(0, index))
saveActivePosition(Math.max(0, index))
}
const moveToCurrentTag = () => {
@ -106,7 +106,7 @@ export const useTags = () => {
scrollbar.moveToTarget(tag)
if (tag.to.fullPath !== route.value.fullPath) {
store.dispatch('tags/updateTagList', route.value)
updateTagList(route.value)
}
break
}
@ -116,13 +116,13 @@ export const useTags = () => {
onBeforeMount(() => {
initTags()
addTag()
addTagList()
moveToCurrentTag()
})
watch(route, (newRoute, oldRoute) => {
saveActivePosition(oldRoute) // 保存标签的位置
addTag()
saveTagPosition(oldRoute) // 保存标签的位置
addTagList()
moveToCurrentTag()
})

View File

@ -13,7 +13,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 17:50:46
* @LastEditTime: 2022-09-24 20:38:36
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -43,12 +43,15 @@
@click.middle="closeTag(tag)"
@contextmenu.prevent="openMenu(tag, $event)"
>
<span class="title">{{ tag.title }}</span>
<span
<span class="title">{{ $t(tag.title) }}</span>
<el-icon
v-if="!isAffix(tag)"
class="el-icon-close"
@click.prevent.stop="closeTag(tag)"
/>
>
<Close />
</el-icon>
</div>
</router-link>
</el-scrollbar>
@ -58,12 +61,14 @@
:style="{ left: left + 'px', top: top + 'px' }"
class="contextmenu"
>
<li @click="refreshSelectedTag(selectedTag)">刷新</li>
<li v-if="!isAffix(selectedTag)" @click="closeTag(selectedTag)">关闭</li>
<li @click="closeOtherTags">关闭其他</li>
<li @click="closeLeftTags">关闭左侧</li>
<li @click="closeRightTags">关闭右侧</li>
<li @click="closeAllTags">关闭全部</li>
<li @click="refreshSelectedTag(selectedTag)">{{ $t('tags.refresh') }}</li>
<li v-if="!isAffix(selectedTag)" @click="closeTag(selectedTag)">
{{ $t('tags.close') }}
</li>
<li @click="closeOtherTags">{{ $t('tags.other') }}</li>
<li @click="closeLeftTags">{{ $t('tags.left') }}</li>
<li @click="closeRightTags">{{ $t('tags.right') }}</li>
<li @click="closeAllTags">{{ $t('tags.all') }}</li>
</ul>
</template>
@ -71,7 +76,7 @@
import { defineComponent, computed, getCurrentInstance } from 'vue'
import { useTags } from './hooks/useTags'
import { useContextMenu } from './hooks/useContextMenu'
import { useStore } from 'vuex'
import { useLayoutsettings } from '@/pinia/modules/layoutSettings'
export default defineComponent({
name: 'Tagsbar',
@ -80,9 +85,8 @@ export default defineComponent({
instance.appContext.config.globalProperties.$tagsbar = this
},
setup() {
const store = useStore()
const defaultSettings = computed(() => store.state.layoutSettings)
const isTagsbarShow = computed(() => defaultSettings.value.tagsbar.isShow)
const defaultSettings = useLayoutsettings()
const isTagsbarShow = computed(() => defaultSettings.tagsbar.isShow)
const tags = useTags()
const contextMenu = useContextMenu(tags.tagList)
@ -156,7 +160,7 @@ export default defineComponent({
margin-left: 8px;
width: 16px;
height: 16px;
vertical-align: 2px;
vertical-align: -2px;
border-radius: 50%;
text-align: center;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);

View File

@ -27,7 +27,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-07-23 17:22:14
* @LastEditTime: 2022-09-27 17:38:48
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -50,29 +50,38 @@
:class="{ no_link: index === breadcrumbs.length - 1 }"
:to="index < breadcrumbs.length - 1 ? item.path : ''"
>
{{ item.meta.title }}
{{ $t(item.meta.title) }}
</el-breadcrumb-item>
</el-breadcrumb>
</template>
<script>
import { defineComponent, computed, ref, onBeforeMount, watch } from 'vue'
import { useApp } from '@/pinia/modules/app'
import { useLayoutsettings } from '@/pinia/modules/layoutSettings'
import { storeToRefs } from 'pinia'
import {
defineComponent,
computed,
ref,
onBeforeMount,
watch,
getCurrentInstance,
} from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
export default defineComponent({
setup(props, { emit }) {
const store = useStore()
const device = computed(() => store.state.app.device)
const { proxy } = getCurrentInstance()
const { device } = storeToRefs(useApp())
const router = useRouter()
const route = router.currentRoute // 使useRoutewatch
const breadcrumbs = ref([])
const defaultSettings = computed(() => store.state.layoutSettings)
const defaultSettings = useLayoutsettings()
const isHorizontalMenu = computed(
() => defaultSettings.value.menus.mode === 'horizontal'
() => defaultSettings.menus.mode === 'horizontal'
)
const getBreadcrumbs = route => {
const home = [{ path: '/', meta: { title: '首页' } }]
const home = [{ path: '/', meta: { title: proxy.$t('menu.homepage') } }]
if (route.name === 'home') {
return home
} else {
@ -91,6 +100,7 @@ export default defineComponent({
watch(
route,
newRoute => {
route.value.meta.truetitle = proxy.$t(route.value.meta.title)
breadcrumbs.value = getBreadcrumbs(newRoute)
emit('on-breadcrumbs-change', breadcrumbs.value.length > 1)
},
@ -118,9 +128,9 @@ export default defineComponent({
::v-deep(.is-link) {
font-weight: normal;
}
::v-deep(.el-breadcrumb__item) {
float: none;
}
// ::v-deep(.el-breadcrumb__item) {
// float: none;
// }
.no_link {
::v-deep(.el-breadcrumb__inner) {
color: #97a8be !important;

View File

@ -0,0 +1,94 @@
<!--
* _oo0oo_
* o8888888o
* 88" . "88
* (| -_- |)
* 0\ = /0
* ___/`---'\___
* .' \\| |// '.
* / \\||| : |||// \
* / _||||| -:- |||||- \
* | | \\\ - /// | |
* | \_| ''\---/'' |_/ |
* \ .-\__ '-' ___/-. /
* ___'. .' /--.--\ `. .'___
* ."" '< `.___\_<|>_/___.' >' "".
* | | : `- \`.;`\ _ /`;.`/ - ` : | |
* \ \ `_. \_ __\ /__ _/ .-` / /
* =====`-.____`.___ \_____/___.-`___.-'=====
* `=---='
*
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* 佛祖保佑 永不宕机 永无BUG
*
* 佛曰:
* 写字楼里写字间写字间里程序员
* 程序人员写程序又拿程序换酒钱
* 酒醒只在网上坐酒醉还来网下眠
* 酒醉酒醒日复日网上网下年复年
* 但愿老死电脑间不愿鞠躬老板前
* 奔驰宝马贵者趣公交自行程序员
* 别人笑我忒疯癫我笑自己命太贱
* 不见满街漂亮妹哪个归得程序员
*
* @Descripttion:
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2022-09-26 11:53:15
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
-->
<template>
<el-dropdown trigger="hover">
<div class="change-lang">
<svg-icon name="language" class="icon"></svg-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
@click="changeLang(item.value)"
v-for="item in langlist"
:key="item.value"
>
{{ item.name }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script setup>
import useLang from '@/i18n/useLang'
const langlist = [
{
name: '简体中文',
value: 'zh-cn',
},
{
name: 'English',
value: 'en',
},
]
const { changeLang } = useLang()
</script>
<style lang="scss" scoped>
.change-lang {
padding: 0 16px;
height: 48px;
cursor: pointer;
display: flex;
align-items: center;
&:hover {
background: #f5f5f5;
}
.icon {
font-size: 18px;
}
}
</style>

View File

@ -18,7 +18,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 12:47:40
* @LastEditTime: 2022-09-27 18:43:44
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -26,25 +26,30 @@
-->
<template>
<i
class="fold-btn el-icon-s-fold"
<el-icon
:size="20"
class="fold-btn"
:class="{ collapse: collapse }"
@click="handleToggleMenu"
></i>
>
<Fold />
</el-icon>
</template>
<script>
import { defineComponent, computed } from 'vue'
import { useStore } from 'vuex'
import { useApp } from '@/pinia/modules/app'
import { storeToRefs } from 'pinia'
import { computed, defineComponent } from 'vue'
export default defineComponent({
setup() {
const store = useStore()
const collapse = computed(() => !!store.state.app.sidebar.collapse)
const appStore = useApp()
const { sidebar } = storeToRefs(appStore)
const { setCollapse } = appStore
const handleToggleMenu = () => {
store.commit('app/setCollapse', +!collapse.value)
setCollapse(+!sidebar.value.collapse)
}
return {
collapse,
collapse: computed(() => sidebar.value.collapse),
handleToggleMenu,
}
},
@ -54,11 +59,7 @@ export default defineComponent({
.fold-btn {
line-height: 48px;
padding: 0 10px;
font-size: 18px;
cursor: pointer;
&:hover {
background: #f5f5f5;
}
&.collapse {
transform: scale(-1, 1);
}

View File

@ -44,7 +44,7 @@
* @version:
* @Date: 2021-04-23 14:15:50
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-28 09:23:23
* @LastEditTime: 2022-09-27 17:55:16
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -52,17 +52,24 @@
-->
<template>
<el-dropdown-item @click="dialogVisible = true">锁定屏幕</el-dropdown-item>
<el-dropdown-item @click="dialogVisible = true">
{{ $t('topbar.lock-title') }}
</el-dropdown-item>
<el-dialog
title="锁定屏幕"
:title="$t('topbar.lock-title')"
v-model="dialogVisible"
width="640px"
custom-class="lock-modal"
append-to-body
>
<Avatar />
<el-form :model="lockModel" :rules="lockRules" ref="lockForm">
<el-form-item label="锁屏密码" prop="password">
<el-form
:model="lockModel"
:rules="lockRules"
ref="lockForm"
label-width="90px"
>
<el-form-item :label="$t('topbar.lock-password')" prop="password">
<el-input
type="password"
v-model.trim="lockModel.password"
@ -71,13 +78,8 @@
></el-input>
</el-form-item>
<el-form-item>
<el-button
size="small"
class="submit-btn"
type="primary"
@click="submitForm"
>
锁定屏幕
<el-button class="submit-btn" type="primary" @click="submitForm">
{{ $t('topbar.lock-title') }}
</el-button>
</el-form-item>
</el-form>
@ -85,25 +87,27 @@
</template>
<script>
import { defineComponent, reactive, ref } from 'vue'
import { defineComponent, getCurrentInstance, reactive, ref } from 'vue'
import Avatar from '@/components/Avatar/index.vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { useApp } from '@/pinia/modules/app'
export default defineComponent({
components: {
Avatar,
},
setup() {
const { proxy } = getCurrentInstance()
const router = useRouter()
const store = useStore()
const dialogVisible = ref(false)
const lockForm = ref(null)
const lockModel = reactive({
password: '',
})
const lockRules = reactive({
password: [{ required: true, message: '请输入锁屏密码' }],
password: [
{ required: true, message: proxy.$t('topbar.lock-rules-password') },
],
})
const submitForm = () => {
lockForm.value.validate(valid => {
@ -112,7 +116,7 @@ export default defineComponent({
}
// token
store.dispatch('app/setScreenCode', lockModel.password)
useApp().setScreenCode(lockModel.password)
//
router.push('/lock?redirect=' + router.currentRoute.value.fullPath)
@ -135,8 +139,3 @@ export default defineComponent({
max-width: 90%;
}
</style>
<style lang="scss" scoped>
.submit-btn {
width: 100%;
}
</style>

View File

@ -37,7 +37,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 15:39:30
* @LastEditTime: 2022-09-27 17:56:21
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -45,7 +45,7 @@
-->
<template>
<el-dropdown trigger="click">
<el-dropdown trigger="hover">
<div class="userinfo">
<template v-if="!userinfo">
<i class="el-icon-user" />
@ -58,27 +58,28 @@
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>个人中心</el-dropdown-item>
<el-dropdown-item>修改密码</el-dropdown-item>
<el-dropdown-item>{{ $t('topbar.center') }}</el-dropdown-item>
<el-dropdown-item>{{ $t('topbar.password') }}</el-dropdown-item>
<lock-modal />
<el-dropdown-item @click="logout">退出登录</el-dropdown-item>
<el-dropdown-item @click="logout">
{{ $t('topbar.logout') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script>
import { defineComponent } from 'vue'
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'
import { useUserinfo } from '@/components/Avatar/hooks/useUserinfo'
import LockModal from './LockModal.vue'
import { useApp } from '@/pinia/modules/app'
export default defineComponent({
components: {
LockModal,
},
setup() {
const store = useStore()
const router = useRouter()
const { userinfo } = useUserinfo()
@ -86,7 +87,7 @@ export default defineComponent({
// 退
const logout = () => {
// token
store.dispatch('app/clearToken')
useApp().clearToken()
router.push('/login')
}

View File

@ -37,7 +37,7 @@
* @version:
* @Date: 2021-04-21 09:18:32
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-07-23 16:49:39
* @LastEditTime: 2022-09-27 18:36:16
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -58,6 +58,7 @@
<div class="action">
<error-log />
<userinfo />
<change-lang />
</div>
</div>
</template>
@ -67,8 +68,11 @@ import Logo from '@/layout/components/Sidebar/Logo.vue'
import Hamburger from './Hamburger.vue'
import Breadcrumbs from './Breadcrumbs.vue'
import Userinfo from './Userinfo.vue'
import ChangeLang from './ChangeLang.vue'
import ErrorLog from '@/components/ErrorLog/index.vue'
import { useStore } from 'vuex'
import { useLayoutsettings } from '@/pinia/modules/layoutSettings'
import { storeToRefs } from 'pinia'
import { useApp } from '@/pinia/modules/app'
export default defineComponent({
components: {
@ -76,16 +80,16 @@ export default defineComponent({
Hamburger,
Breadcrumbs,
Userinfo,
ChangeLang,
ErrorLog,
},
setup() {
const store = useStore()
const defaultSettings = computed(() => store.state.layoutSettings)
const defaultSettings = useLayoutsettings()
const device = computed(() => store.state.app.device)
const { device } = storeToRefs(useApp())
const isHorizontalMenu = computed(
() => defaultSettings.value.menus.mode === 'horizontal'
() => defaultSettings.menus.mode === 'horizontal'
)
const isShowLogo = computed(
@ -95,7 +99,7 @@ export default defineComponent({
const isShowHamburger = computed(() => !isHorizontalMenu.value)
const isShowBreadcrumbs = computed(
() => defaultSettings.value.breadcrumbs.isShow && !isHorizontalMenu.value
() => defaultSettings.breadcrumbs.isShow && !isHorizontalMenu.value
)
return {

View File

@ -24,22 +24,23 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-29 11:31:50
* @LastEditTime: 2022-09-27 19:02:14
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
import { onBeforeMount, onBeforeUnmount /*watch*/ } from 'vue'
// import { useRouter } from 'vue-router';
import { useStore } from 'vuex'
import { storeToRefs } from 'pinia'
import { useApp } from '@/pinia/modules/app'
import { onBeforeMount, onBeforeUnmount, computed } from 'vue'
const WIDTH = 768
export const useResizeHandler = () => {
const store = useStore()
// const router = useRouter();
// const route = router.currentRoute;
const appStore = useApp()
const { sidebar } = storeToRefs(appStore)
const { setDevice, setCollapse } = appStore
const collapse = computed(() => sidebar.value.collapse)
const isMobile = () => {
return window.innerWidth < WIDTH
@ -47,11 +48,11 @@ export const useResizeHandler = () => {
const resizeHandler = () => {
if (isMobile()) {
store.commit('app/setDevice', 'mobile')
store.commit('app/setCollapse', 1)
setDevice('mobile')
setCollapse(1)
} else {
store.commit('app/setDevice', 'desktop')
store.commit('app/setCollapse', 0)
setDevice('desktop')
setCollapse(collapse.value)
}
}
@ -63,11 +64,4 @@ export const useResizeHandler = () => {
onBeforeUnmount(() => {
window.removeEventListener('resize', resizeHandler)
})
// // 监听路由的时候不能使用useRoute获取路由否则会有警告
// watch(route, () => {
// if (store.state.app.device === 'mobile' && !store.state.app.sidebar.collapse) {
// store.commit('app/setCollapse', 1)
// }
// })
}

View File

@ -27,7 +27,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 14:58:53
* @LastEditTime: 2022-09-27 18:31:47
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -62,7 +62,8 @@ import Tagsbar from './components/Tagsbar/index.vue'
import Breadcrumbs from './components/Topbar/Breadcrumbs.vue'
import Content from './components/Content/index.vue'
import { useResizeHandler } from './hooks/useResizeHandler'
import { useStore } from 'vuex'
import { storeToRefs } from 'pinia'
import { useLayoutsettings } from '@/pinia/modules/layoutSettings'
export default defineComponent({
name: 'layout',
@ -76,16 +77,13 @@ export default defineComponent({
},
setup() {
useResizeHandler()
const store = useStore()
const defaultSettings = computed(() => store.state.layoutSettings)
const isFluid = computed(() => defaultSettings.value.layout.isFluid)
const isTopbarFixed = computed(() => defaultSettings.value.topbar.isFixed)
const isMenusShow = computed(() => defaultSettings.value.menus.isShow)
const isHorizontalMenu = computed(
() => defaultSettings.value.menus.mode === 'horizontal'
)
const defaultSettings = useLayoutsettings()
const isFluid = defaultSettings.layout.isFluid
const isTopbarFixed = defaultSettings.topbar.isFixed
const isMenusShow = defaultSettings.menus.isShow
const isHorizontalMenu = defaultSettings.menus.mode === 'horizontal'
const isBreadcrumbsShow = computed(
() => isHorizontalMenu.value && defaultSettings.value.breadcrumbs.isShow
() => isHorizontalMenu && defaultSettings.breadcrumbs.isShow
)
const paddingFlag = ref(true)
const handleBreadcrumbsChange = boo => {

View File

@ -27,7 +27,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-29 10:08:00
* @LastEditTime: 2022-09-27 19:04:15
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -42,15 +42,22 @@ const app = createApp(App)
// 引入element-plus
import ElementPlus from 'element-plus'
import './assets/style/element-variables.scss'
// 引入中文语言包
import 'dayjs/locale/zh-cn'
import locale from 'element-plus/lib/locale/lang/zh-cn'
// 国际化
import i18n from '@/i18n'
// 全局注册element-plus/icons-vue
import * as ICONS from '@element-plus/icons-vue'
Object.entries(ICONS).forEach(([key, component]) => {
// app.component(key === 'PieChart' ? 'PieChartIcon' : key, component)
app.component(key, component)
})
// 引入路由
import router from './router'
// 引入store
import store from './store'
// 引入pinia
import pinia from './pinia'
// 权限控制
import './permission'
@ -73,9 +80,8 @@ import useErrorHandler from './error-log'
useErrorHandler(app)
app
.use(ElementPlus, {
locale,
})
.use(store)
.use(i18n)
.use(ElementPlus)
.use(pinia)
.use(router)
.mount('#app')

View File

@ -26,7 +26,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-29 09:47:46
* @LastEditTime: 2022-09-27 16:35:06
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -34,12 +34,16 @@
*/
import { ElLoading } from 'element-plus'
import router, { asyncRoutes } from '@/router'
import store from '@/store'
import router from '@/router'
// import store from '@/store'
import { TOKEN } from '@/store/modules/app' // TOKEN变量名
import { nextTick } from 'vue'
import { useApp } from './pinia/modules/app'
import { useAccount } from './pinia/modules/account'
import { useMenus } from './pinia/modules/menu'
const getPageTitle = title => {
const appTitle = store.state.app.title
const { title: appTitle } = useApp()
if (title) {
return `${title} - ${appTitle}`
}
@ -49,9 +53,15 @@ const getPageTitle = title => {
// 白名单里面是路由对象的name
const WhiteList = ['login', 'lock']
let loadingInstance = null
// vue-router4的路由守卫不再是通过next放行而是通过return返回true或false或者一个路由地址
router.beforeEach(async to => {
document.title = getPageTitle(!!to.meta && to.meta.title)
loadingInstance = ElLoading.service({
lock: true,
// text: '正在加载数据,请稍候~',
background: 'rgba(0, 0, 0, 0.7)',
})
if (WhiteList.includes(to.name)) {
return true
@ -65,40 +75,25 @@ router.beforeEach(async to => {
replace: true,
}
} else {
const { userinfo, getUserinfo } = useAccount()
// 获取用户角色信息,根据角色判断权限
let userinfo = store.state.account.userinfo
if (!userinfo) {
const loadingInstance = ElLoading.service({
lock: true,
text: '正在加载数据,请稍候~',
background: 'rgba(0, 0, 0, 0.7)',
})
try {
// 获取用户信息
userinfo = await store.dispatch('account/getUserinfo')
loadingInstance.close()
await getUserinfo()
} catch (err) {
loadingInstance.close()
return false
}
// 删除所有动态路由
asyncRoutes.forEach(item => {
router.removeRoute(item.name)
})
return to.fullPath
}
// 生成菜单(如果你的项目有动态菜单,在此处会添加动态路由)
if (store.state.menu.menus.length <= 0) {
const loadingInstance = ElLoading.service({
lock: true,
text: '正在加载数据,请稍候~',
background: 'rgba(0, 0, 0, 0.7)',
})
const { menus, generateMenus } = useMenus()
if (menus.length <= 0) {
try {
await store.dispatch('menu/generateMenus', userinfo)
loadingInstance.close()
await generateMenus()
return to.fullPath // 添加动态路由后必须加这一句触发重定向否则会404
} catch (err) {
loadingInstance.close()
@ -108,7 +103,7 @@ router.beforeEach(async to => {
// 判断是否处于锁屏状态
if (to.name !== 'lock') {
const { authorization } = store.state.app
const { authorization } = useApp()
if (!!authorization && !!authorization.screenCode) {
return {
name: 'lock',
@ -121,3 +116,12 @@ router.beforeEach(async to => {
}
}
})
router.afterEach(to => {
loadingInstance.close()
if (router.currentRoute.value.name === to.name) {
nextTick(() => {
document.title = getPageTitle(!!to.meta && to.meta.truetitle)
})
}
})

View File

@ -14,22 +14,15 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 12:48:11
* @LastEditTime: 2022-09-27 14:52:09
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
import { createStore } from 'vuex'
import { createPinia } from 'pinia'
const modulesFiles = import.meta.globEager('./modules/*.js')
const modules = Object.entries(modulesFiles).reduce((modules, [path, mod]) => {
const moduleName = path.replace(/^\.\/modules\/(.*)\.\w+$/, '$1')
modules[moduleName] = mod.default
return modules
}, {})
const pinia = createPinia()
export default createStore({
modules,
})
export default pinia

View File

@ -3,38 +3,32 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-29 10:10:14
* @LastEditTime: 2022-09-27 14:57:06
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
import { defineStore } from 'pinia'
import { GetUserinfo } from '@/api/login'
export default {
namespaced: true,
state: {
export const useAccount = defineStore('account', {
state: () => ({
userinfo: null,
permissionList: [],
},
mutations: {
// 保存用户信息
setUserinfo(state, data) {
state.userinfo = data
},
// 清除用户信息
clearUserinfo(state) {
state.userinfo = null
},
},
}),
actions: {
// 清除用户信息
clearUserinfo() {
this.userinfo = null
},
// 获取用户信息
async getUserinfo({ commit }) {
async getUserinfo() {
const { code, data } = await GetUserinfo()
if (+code === 200) {
commit('setUserinfo', data)
this.userinfo = data
return Promise.resolve(data)
}
},
},
}
})

View File

@ -3,70 +3,67 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-11-15 09:51:45
* @LastEditTime: 2022-09-27 15:42:35
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
import { defineStore } from 'pinia'
import { getItem, setItem, removeItem } from '@/utils/storage' //getItem和setItem是封装的操作localStorage的方法
import { AesEncryption } from '@/utils/encrypt'
import { toRaw } from 'vue'
import { useAccount } from './account'
import { useTags } from './tags'
import { useMenus } from './menu'
export const TOKEN = 'VEA-TOKEN'
const COLLAPSE = 'VEA-COLLAPSE'
export default {
namespaced: true,
state: {
export const useApp = defineStore('app', {
state: () => ({
title: 'Vue3 Element Admin',
authorization: getItem(TOKEN),
sidebar: {
collapse: getItem(COLLAPSE),
},
device: 'desktop',
},
mutations: {
setToken(state, data) {
state.authorization = data
// 保存到localStorage
setItem(TOKEN, data)
},
clearToken(state) {
state.authorization = ''
removeItem(TOKEN)
},
setCollapse(state, data) {
state.sidebar.collapse = data
}),
actions: {
setCollapse(data) {
this.sidebar.collapse = data
// 保存到localStorage
setItem(COLLAPSE, data)
},
clearCollapse(state) {
state.sidebar.collapse = ''
clearCollapse() {
this.sidebar.collapse = ''
removeItem(COLLAPSE)
},
setDevice(state, device) {
state.device = device
setDevice(device) {
this.device = device
},
},
actions: {
setToken({ commit, dispatch }, payload) {
dispatch('clearToken')
commit('setToken', payload)
setToken(data) {
this.authorization = data
// 保存到localStorage
setItem(TOKEN, data)
},
clearToken({ commit }) {
initToken(data) {
this.clearToken()
this.setToken(data)
},
clearToken() {
// 清除token
commit('clearToken')
this.authorization = ''
removeItem(TOKEN)
// 清除用户信息
commit('account/clearUserinfo', '', { root: true })
useAccount().clearUserinfo()
// 清除标签栏
commit('tags/CLEAR_ALL_TAGS', '', { root: true })
useTags().clearAllTags()
// 清空menus
commit('menu/SET_MENUS', [], { root: true })
useMenus().setMenus([])
},
setScreenCode({ commit, state }, password) {
const authorization = toRaw(state.authorization)
setScreenCode(password) {
const authorization = toRaw(this.authorization)
if (!password) {
try {
@ -74,7 +71,10 @@ export default {
} catch (err) {
console.log(err)
}
commit('setToken', authorization)
this.authorization = authorization
// 保存到localStorage
setItem(TOKEN, authorization)
return
}
@ -82,10 +82,13 @@ export default {
// 对密码加密
const screenCode = new AesEncryption().encryptByAES(password)
commit('setToken', {
const res = {
...authorization,
screenCode,
})
}
this.authorization = res
// 保存到localStorage
setItem(TOKEN, res)
},
},
}
})

View File

@ -0,0 +1,29 @@
/*
* @Descripttion:
* @version:
* @Date: 2021-04-21 09:18:32
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2022-09-27 15:45:36
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
import { defineStore } from 'pinia'
export const useErrorlog = defineStore('errorLog', {
state: () => ({
logs: [],
}),
actions: {
addErrorLog(log) {
// 可以根据需要将错误上报给服务器
// ....code.......
this.logs.push(log)
},
clearErrorLog() {
this.logs.splice(0)
},
},
})

View File

@ -27,24 +27,24 @@
* @version:
* @Date: 2021-07-23 16:10:49
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 15:03:26
* @LastEditTime: 2022-09-27 15:47:50
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
import { defineStore } from 'pinia'
import { getItem, setItem } from '@/utils/storage' //getItem和setItem是封装的操作localStorage的方法
import defaultSettings from '@/default-settings'
export default {
namespaced: true,
state: getItem('defaultSettings') || defaultSettings,
mutations: {
SAVE_SETTINGS(state, data) {
export const useLayoutsettings = defineStore('layoutSettings', {
state: () => getItem('defaultSettings') || defaultSettings,
actions: {
saveSettings(data) {
Object.entries(data).forEach(([key, value]) => {
state[key] = value
this[key] = value
})
setItem('defaultSettings', data)
},
},
}
})

106
src/pinia/modules/menu.js Normal file
View File

@ -0,0 +1,106 @@
/*
* @Descripttion:
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2022-09-27 16:41:46
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
import { defineStore } from 'pinia'
import { fixedRoutes, asyncRoutes } from '@/router'
import { GetMenus } from '@/api/menu'
import router from '@/router'
import { ref } from 'vue'
export const useMenus = defineStore('menu', () => {
const generateUrl = (path, parentPath) => {
return path.startsWith('/')
? path
: path
? `${parentPath}/${path}`
: parentPath
}
const getFilterRoutes = (targetRoutes, ajaxRoutes) => {
const filterRoutes = []
ajaxRoutes.forEach(item => {
const target = targetRoutes.find(target => target.name === item.name)
if (target) {
const { children: targetChildren, ...rest } = target
const route = {
...rest,
}
if (item.children) {
route.children = getFilterRoutes(targetChildren, item.children)
}
filterRoutes.push(route)
}
})
return filterRoutes
}
const getFilterMenus = (arr, parentPath = '') => {
const menus = []
arr.forEach(item => {
if (!item.hidden) {
const menu = {
url: generateUrl(item.path, parentPath),
title: item.meta.title,
icon: item.icon,
}
if (item.children) {
if (item.children.filter(child => !child.hidden).length <= 1) {
menu.url = generateUrl(item.children[0].path, menu.url)
} else {
menu.children = getFilterMenus(item.children, menu.url)
}
}
menus.push(menu)
}
})
return menus
}
const menus = ref([])
const setMenus = data => {
menus.value = data
}
const generateMenus = async () => {
// // 方式一:只有固定菜单
// const menus = getFilterMenus(fixedRoutes)
// commit('SET_MENUS', menus)
// 方式二:有动态菜单
// 从后台获取菜单
const { code, data } = await GetMenus()
if (+code === 200) {
// 添加路由之前先删除所有动态路由
asyncRoutes.forEach(item => {
router.removeRoute(item.name)
})
// 过滤出需要添加的动态路由
const filterRoutes = getFilterRoutes(asyncRoutes, data)
filterRoutes.forEach(route => router.addRoute(route))
// 生成菜单
const menus = getFilterMenus([...fixedRoutes, ...filterRoutes])
setMenus(menus)
}
}
return {
menus,
setMenus,
generateMenus,
}
})

113
src/pinia/modules/tags.js Normal file
View File

@ -0,0 +1,113 @@
/*
* @Descripttion:
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2022-09-27 16:49:31
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
import { defineStore } from 'pinia'
import { getItem, setItem, removeItem } from '@/utils/storage' //getItem和setItem是封装的操作localStorage的方法
const TAGLIST = 'VEA-TAGLIST'
export const useTags = defineStore('tags', {
state: () => ({
tagList: getItem(TAGLIST) || [],
cacheList: [],
activePosition: -1,
}),
actions: {
saveActivePosition(index) {
this.activePosition = index
},
addTag({ path, fullPath, name, meta, params, query }) {
if (this.tagList.some(v => v.path === path)) return false
// 添加tagList
const target = Object.assign(
{},
{ path, fullPath, name, meta, params, query },
{
title: meta.title || '未命名',
fullPath: fullPath || path,
}
)
if (this.activePosition === -1) {
if (name === 'home') {
this.tagList.unshift(target)
} else {
this.tagList.push(target)
}
} else {
this.tagList.splice(this.activePosition + 1, 0, target)
}
// 保存到localStorage
setItem(TAGLIST, this.tagList)
// 添加cacheList
if (this.cacheList.includes(name)) return
if (!meta.noCache) {
this.cacheList.push(name)
}
},
deTagList(tag) {
// 删除tagList
this.tagList = this.tagList.filter(v => v.path !== tag.path)
// 保存到localStorage
setItem(TAGLIST, this.tagList)
},
deCacheList(tag) {
// 删除cacheList
this.cacheList = this.cacheList.filter(v => v !== tag.name)
},
delTag(tag) {
// 删除tagList
this.deTagList(tag)
// 删除cacheList
this.deCacheList(tag)
},
delOtherTags(tag) {
this.tagList = this.tagList.filter(
v => !!v.meta.affix || v.path === tag.path
)
// 保存到localStorage
setItem(TAGLIST, this.tagList)
this.cacheList = this.cacheList.filter(v => v === tag.name)
},
delSomeTags(tags) {
this.tagList = this.tagList.filter(
v => !!v.meta.affix || tags.every(tag => tag.path !== v.path)
)
// 保存到localStorage
setItem(TAGLIST, this.tagList)
this.cacheList = this.cacheList.filter(v =>
tags.every(tag => tag.name !== v)
)
},
delAllTags() {
this.tagList = this.tagList.filter(v => !!v.meta.affix)
// 保存到localStorage
removeItem(TAGLIST)
this.cacheList = []
},
updateTagList(tag) {
const index = this.tagList.findIndex(v => v.path === tag.path)
if (index > -1) {
this.tagList[index] = Object.assign({}, this.tagList[index], tag)
// 保存到localStorage
setItem(TAGLIST, this.tagList)
}
},
clearAllTags() {
this.cacheList = []
this.tagList = []
// 保存到localStorage
removeItem(TAGLIST)
},
},
})

View File

@ -3,16 +3,17 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 16:23:58
* @LastEditTime: 2022-09-27 18:14:03
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
import store from '@/store'
import { useAccount } from '@/pinia/modules/account'
const checkUserinfo = (code, fullPath) => {
const userinfo = store.state.account.userinfo
const { userinfo } = useAccount()
if (userinfo) {
return `/error/${code === '404' ? fullPath : code}`
}

View File

@ -3,7 +3,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-07-26 14:37:08
* @LastEditTime: 2022-09-24 19:27:21
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -19,16 +19,16 @@ export default [
component: Layout,
name: 'Dashboard',
meta: {
title: '工作台',
title: 'menu.dashboard',
},
icon: 'home',
icon: 'icon-home',
children: [
{
path: '',
name: 'home',
component: Home,
meta: {
title: '首页',
title: 'menu.homepage',
affix: true,
},
},

View File

@ -3,7 +3,7 @@
* @version:
* @Date: 2021-04-21 09:18:32
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-07-26 16:30:58
* @LastEditTime: 2022-09-27 18:51:35
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -28,16 +28,16 @@ export default [
component: Layout,
name: 'test',
meta: {
title: '测试页面',
title: 'menu.test',
},
icon: 'el-icon-location',
icon: 'Location',
children: [
{
path: '',
name: 'testList',
component: List,
meta: {
title: '列表',
title: 'menu.testList',
},
},
{
@ -45,7 +45,7 @@ export default [
name: 'testAdd',
component: Add,
meta: {
title: '添加',
title: 'menu.testAdd',
},
hidden: true, // 不在菜单中显示
},
@ -54,33 +54,33 @@ export default [
name: 'testEdit',
component: Edit,
meta: {
title: '编辑',
title: 'menu.testEdit',
},
hidden: true, // 不在菜单中显示
},
{
path: 'auth',
name: 'testAuth',
component: Auth,
meta: {
title: '权限测试',
},
},
{
path: 'noauth',
name: 'testNoAuth',
component: NoAuth,
meta: {
title: '权限页面',
},
hidden: true,
},
// {
// path: 'auth',
// name: 'testAuth',
// component: Auth,
// meta: {
// title: 'menu.testAuth',
// },
// },
// {
// path: 'noauth',
// name: 'testNoAuth',
// component: NoAuth,
// meta: {
// title: 'menu.testNoAuth',
// },
// hidden: true,
// },
{
path: 'cache',
name: 'test-cache',
component: Iscache,
meta: {
title: '该页面可缓存',
title: 'menu.test-cache',
},
},
{
@ -88,7 +88,7 @@ export default [
name: 'test-no-cache',
component: Nocache,
meta: {
title: '该页面不缓存',
title: 'menu.test-no-cache',
noCache: true, // 不缓存页面
},
},
@ -98,7 +98,7 @@ export default [
component: Nest,
redirect: '/test/nest/page1',
meta: {
title: '二级菜单',
title: 'menu.nest',
},
children: [
{
@ -106,7 +106,7 @@ export default [
name: 'nestPage1',
component: NestPage1,
meta: {
title: 'page1',
title: 'menu.nestPage1',
},
},
{
@ -114,7 +114,7 @@ export default [
name: 'nestPage2',
component: NestPage2,
meta: {
title: 'page2',
title: 'menu.nestPage2',
},
},
],
@ -124,7 +124,7 @@ export default [
name: 'test-error-log',
component: ErrorLog,
meta: {
title: '测试错误日志',
title: 'menu.test-error-log',
},
},
],

View File

@ -1,43 +0,0 @@
/*
* @Descripttion:
* @version:
* @Date: 2021-04-21 09:18:32
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 09:34:13
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
const state = {
logs: [],
}
const mutations = {
ADD_ERROR_LOG: (state, log) => {
state.logs.push(log)
},
CLEAR_ERROR_LOG: state => {
state.logs.splice(0)
},
}
const actions = {
addErrorLog({ commit }, log) {
// 可以根据需要将错误上报给服务器
// ....code.......
// 触发mutations
commit('ADD_ERROR_LOG', log)
},
clearErrorLog({ commit }) {
commit('CLEAR_ERROR_LOG')
},
}
export default {
namespaced: true,
state,
mutations,
actions,
}

View File

@ -1,109 +0,0 @@
/*
* @Descripttion:
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-07-26 18:22:01
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
import { fixedRoutes, asyncRoutes } from '@/router'
import { GetMenus } from '@/api/menu'
import router from '@/router'
// const hasPermission = (role, route) => {
// if (!!route.meta && !!route.meta.roles && !route.meta.roles.includes(role)) {
// return false
// }
// return true
// }
const generateUrl = (path, parentPath) => {
return path.startsWith('/')
? path
: path
? `${parentPath}/${path}`
: parentPath
}
const getFilterRoutes = (targetRoutes, ajaxRoutes) => {
const filterRoutes = []
ajaxRoutes.forEach(item => {
const target = targetRoutes.find(target => target.name === item.name)
if (target) {
const { children: targetChildren, ...rest } = target
const route = {
...rest,
}
if (item.children) {
route.children = getFilterRoutes(targetChildren, item.children)
}
filterRoutes.push(route)
}
})
return filterRoutes
}
const getFilterMenus = (arr, parentPath = '') => {
const menus = []
arr.forEach(item => {
if (!item.hidden) {
const menu = {
url: generateUrl(item.path, parentPath),
title: item.meta.title,
icon: item.icon,
}
if (item.children) {
if (item.children.filter(child => !child.hidden).length <= 1) {
menu.url = generateUrl(item.children[0].path, menu.url)
} else {
menu.children = getFilterMenus(item.children, menu.url)
}
}
menus.push(menu)
}
})
return menus
}
export default {
namespaced: true,
state: {
menus: [],
},
mutations: {
SET_MENUS(state, data) {
state.menus = data
},
},
actions: {
async generateMenus({ commit }, userinfo) {
// // 方式一:只有固定菜单
// const menus = getFilterMenus(fixedRoutes)
// commit('SET_MENUS', menus)
// 方式二:有动态菜单
// 从后台获取菜单
const { code, data } = await GetMenus({ role: userinfo.role })
if (+code === 200) {
// 过滤出需要添加的动态路由
const filterRoutes = getFilterRoutes(asyncRoutes, data)
filterRoutes.forEach(route => router.addRoute(route))
// 生成菜单
const menus = getFilterMenus([...fixedRoutes, ...filterRoutes])
commit('SET_MENUS', menus)
}
},
},
}

View File

@ -1,150 +0,0 @@
/*
* @Descripttion:
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 15:36:04
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
*/
import { getItem, setItem, removeItem } from '@/utils/storage' //getItem和setItem是封装的操作localStorage的方法
const TAGLIST = 'VEA-TAGLIST'
const state = {
tagList: getItem(TAGLIST) || [],
cacheList: [],
activePosition: -1,
}
const mutations = {
ADD_TAG_LIST: (state, { path, fullPath, name, meta, params, query }) => {
if (state.tagList.some(v => v.path === path)) return false
const target = Object.assign(
{},
{ path, fullPath, name, meta, params, query },
{
title: meta.title || '未命名',
fullPath: fullPath || path,
}
)
if (state.activePosition === -1) {
if (name === 'home') {
state.tagList.unshift(target)
} else {
state.tagList.push(target)
}
} else {
state.tagList.splice(state.activePosition + 1, 0, target)
}
// 保存到localStorage
setItem(TAGLIST, state.tagList)
},
ADD_CACHE_LIST: (state, tag) => {
if (state.cacheList.includes(tag.name)) return
if (!tag.meta.noCache) {
state.cacheList.push(tag.name)
}
},
DEL_TAG_LIST: (state, tag) => {
state.tagList = state.tagList.filter(v => v.path !== tag.path)
// 保存到localStorage
setItem(TAGLIST, state.tagList)
},
DEL_CACHE_LIST: (state, tag) => {
state.cacheList = state.cacheList.filter(v => v !== tag.name)
},
DEL_OTHER_TAG_LIST: (state, tag) => {
state.tagList = state.tagList.filter(
v => !!v.meta.affix || v.path === tag.path
)
// 保存到localStorage
setItem(TAGLIST, state.tagList)
},
DEL_OTHER_CACHE_LIST: (state, tag) => {
state.cacheList = state.cacheList.filter(v => v === tag.name)
},
DEL_SOME_TAG_LIST: (state, tags) => {
state.tagList = state.tagList.filter(
v => !!v.meta.affix || tags.every(tag => tag.path !== v.path)
)
// 保存到localStorage
setItem(TAGLIST, state.tagList)
},
DEL_SOME_CACHE_LIST: (state, tags) => {
state.cacheList = state.cacheList.filter(v =>
tags.every(tag => tag.name !== v)
)
},
DEL_ALL_TAG_LIST: state => {
state.tagList = state.tagList.filter(v => !!v.meta.affix)
// 保存到localStorage
removeItem(TAGLIST)
},
DEL_ALL_CACHE_LIST: state => {
state.cacheList = []
},
CLEAR_ALL_TAGS: state => {
state.cacheList = []
state.tagList = []
// 保存到localStorage
removeItem(TAGLIST)
},
UPDATE_TAG_LIST: (state, tag) => {
const index = state.tagList.findIndex(v => v.path === tag.path)
if (index > -1) {
state.tagList[index] = Object.assign({}, state.tagList[index], tag)
// 保存到localStorage
setItem(TAGLIST, state.tagList)
}
},
SAVE_ACTIVE_POSITION: (state, index) => {
state.activePosition = index
},
}
const actions = {
saveActivePosition({ commit }, index) {
commit('SAVE_ACTIVE_POSITION', index)
},
addTag({ commit }, tag) {
commit('ADD_TAG_LIST', tag)
commit('ADD_CACHE_LIST', tag)
},
delTag({ commit }, tag) {
commit('DEL_TAG_LIST', tag)
commit('DEL_CACHE_LIST', tag)
},
delOtherTags({ commit }, tag) {
commit('DEL_OTHER_TAG_LIST', tag)
commit('DEL_OTHER_CACHE_LIST', tag)
},
delSomeTags({ commit }, tags) {
commit('DEL_SOME_TAG_LIST', tags)
commit('DEL_SOME_CACHE_LIST', tags)
},
delAllTags({ commit }) {
commit('DEL_ALL_TAG_LIST')
commit('DEL_ALL_CACHE_LIST')
},
updateTagList({ commit }, { path, fullPath, name, meta, params, query }) {
commit('UPDATE_TAG_LIST', { path, fullPath, name, meta, params, query })
},
}
export default {
namespaced: true,
state,
mutations,
actions,
}

View File

@ -22,7 +22,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 15:44:39
* @LastEditTime: 2022-09-27 18:17:20
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -31,8 +31,8 @@
import axios from 'axios'
import { ElMessage } from 'element-plus'
import store from '@/store'
import router from '@/router'
import { useApp } from '@/pinia/modules/app'
const service = axios.create({
baseURL: '/',
@ -43,7 +43,7 @@ const service = axios.create({
// 拦截请求
service.interceptors.request.use(
config => {
const { authorization } = store.state.app
const { authorization } = useApp()
if (authorization) {
config.headers.Authorization = `Bearer ${authorization.token}`
}
@ -67,7 +67,7 @@ service.interceptors.response.use(
// 响应拦截器中的 error 就是那个响应的错误对象
if (error.response && error.response.status === 401) {
// 校验是否有 refresh_token
const { authorization } = store.state.app
const { authorization, clearToken, setToken } = useApp()
if (!authorization || !authorization.refresh_token) {
if (router.currentRoute.value.name === 'login') {
return Promise.reject(error)
@ -75,7 +75,7 @@ service.interceptors.response.use(
const redirect = encodeURIComponent(window.location.href)
router.push(`/login?redirect=${redirect}`)
// 清除token
store.dispatch('app/clearToken')
clearToken()
setTimeout(() => {
ElMessage.closeAll()
try {
@ -99,7 +99,7 @@ service.interceptors.response.use(
})
// 如果获取成功,则把新的 token 更新到容器中
// console.log('刷新 token 成功', res)
store.commit('app/setToken', {
setToken({
token: res.data.data.token, // 最新获取的可用 token
refresh_token: authorization.refresh_token, // 还是原来的 refresh_token
})
@ -113,7 +113,7 @@ service.interceptors.response.use(
const redirect = encodeURIComponent(window.location.href)
router.push(`/login?redirect=${redirect}`)
// 清除token
store.dispatch('app/clearToken')
clearToken()
return Promise.reject(error)
}
}

View File

@ -3,7 +3,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-21 09:33:01
* @LastEditTime: 2022-09-24 21:52:50
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -14,19 +14,19 @@
<template v-if="error === '403'">
<span class="code-403">403</span>
<svg-icon name="error-icons-403" class="error-img" />
<h2 class="title">您无权访问此页面</h2>
<h2 class="title">{{ $t('error.noauth') }}</h2>
</template>
<template v-else-if="error === '500'">
<svg-icon name="error-icons-500" class="error-img" />
<h2 class="title">服务器出错了</h2>
<h2 class="title">{{ $t('error.servererror') }}</h2>
</template>
<template v-else-if="error === '404'">
<svg-icon name="error-icons-404" class="error-img" />
<h2 class="title">您访问的页面不存在</h2>
<h2 class="title">{{ $t('error.notfound') }}</h2>
</template>
<router-link to="/">
<el-button type="primary">返回首页</el-button>
<el-button type="primary">{{ $t('error.backhome') }}</el-button>
</router-link>
</div>
</template>

View File

@ -3,27 +3,12 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-23 15:16:12
* @LastEditTime: 2022-09-24 18:18:43
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
* @Donate: https://huzhushan.gitee.io/vue3-element-admin/donate/
-->
<template>
<div class="home">home</div>
home
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
name: 'home',
setup() {},
})
</script>
<style lang="scss" scoped>
.home {
color: $mainColor;
}
</style>

View File

@ -26,7 +26,7 @@
* @version:
* @Date: 2021-04-23 16:21:00
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-04-23 16:50:50
* @LastEditTime: 2022-09-25 11:10:49
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -34,7 +34,13 @@
-->
<template>
<div class="current-time" v-html="currentTime"></div>
<div class="current-time">
<div class="time">{{ currentTime }}</div>
<div class="date">
{{ currentDate }}
<span style="margin-left: 16px">{{ $t(week) }}</span>
</div>
</div>
</template>
<script>
@ -45,10 +51,7 @@ export default defineComponent({
setup() {
const currentTime = ref(null)
const getTime = () => {
currentTime.value = parseTime(
new Date(),
'<div class="time">{h}:{i}:{s}</div><div class="date">{y}-{m}-{d} 周{a}</div>'
)
currentTime.value = parseTime(new Date(), '{h}:{i}:{s}')
requestAnimationFrame(getTime)
}
@ -58,6 +61,8 @@ export default defineComponent({
return {
currentTime,
currentDate: parseTime(new Date(), '{y}-{m}-{d}'),
week: `topbar.lock-week${new Date().getDay()}`,
}
},
})

View File

@ -37,7 +37,7 @@
* @version:
* @Date: 2021-04-23 19:17:20
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-07-19 10:04:01
* @LastEditTime: 2022-09-27 18:37:28
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -46,10 +46,10 @@
<template>
<h1 class="title">
屏幕已锁定
{{ $t('topbar.lock-locked') }}
<div class="unlock-btn" @click="handleUnlock">
<i class="el-icon-unlock"></i>
解锁
{{ $t('topbar.lock-lock') }}
</div>
</h1>
<div class="unlock-modal" v-show="showModal">
@ -60,14 +60,15 @@
type="password"
v-model.trim="lockModel.password"
autocomplete="off"
placeholder="请输入锁屏密码或登录密码"
:placeholder="$t('topbar.lock-rules-password2')"
@keyup.enter="submitForm"
style="width:320px"
>
<template #append>
<el-button
type="primary"
class="btn-unlock"
icon="el-icon-right"
icon="Right"
:loading="loading"
@click="submitForm"
></el-button>
@ -75,8 +76,12 @@
</el-input>
</el-form-item>
<el-form-item>
<el-button @click="cancel" type="text">取消</el-button>
<el-button @click="reLogin" type="text">重新登录</el-button>
<el-button @click="cancel" type="text">
{{ $t('public.cancel') }}
</el-button>
<el-button @click="reLogin" type="text">
{{ $t('topbar.lock-relogin') }}
</el-button>
</el-form-item>
</el-form>
</div>
@ -86,8 +91,10 @@ import { defineComponent, ref, reactive, getCurrentInstance } from 'vue'
import Avatar from '@/components/Avatar/index.vue'
import { AesEncryption } from '@/utils/encrypt'
import { useRoute, useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { Login } from '@/api/login'
import { useApp } from '@/pinia/modules/app'
import { storeToRefs } from 'pinia'
import { useAccount } from '@/pinia/modules/account'
export default defineComponent({
components: {
@ -95,7 +102,6 @@ export default defineComponent({
},
setup() {
const { proxy: ctx } = getCurrentInstance()
const store = useStore()
const router = useRouter()
const route = useRoute()
const showModal = ref(false)
@ -105,9 +111,15 @@ export default defineComponent({
})
const loading = ref(false)
const appStore = useApp()
const { authorization } = storeToRefs(appStore)
const { clearToken, setScreenCode } = appStore
const accountStore = useAccount()
const { userinfo } = storeToRefs(accountStore)
const { getUserinfo } = accountStore
const checkPwd = async (rule, value, callback) => {
const { authorization } = store.state.app
const cipher = authorization && authorization.screenCode
const cipher = authorization.value && authorization.value.screenCode
if (!cipher) {
return callback()
}
@ -118,7 +130,7 @@ export default defineComponent({
//
loading.value = true
const { code } = await Login({
username: store.state.account.userinfo.name,
username: userinfo.value.name,
password: value,
})
loading.value = false
@ -131,10 +143,10 @@ export default defineComponent({
const lockRules = reactive({
password: [
{ required: true, message: '请输入密码' },
{ required: true, message: ctx.$t('topbar.lock-rules-password2') },
{
validator: checkPwd,
message: '密码错误',
message: ctx.$t('topbar.lock-rules-password3'),
trigger: 'none',
},
],
@ -142,13 +154,14 @@ export default defineComponent({
const handleUnlock = () => {
//
const { authorization } = store.state.app
if (authorization) {
if (authorization.value) {
showModal.value = true
//
!store.state.account.userinfo && store.dispatch('account/getUserinfo')
if (!userinfo.value) {
getUserinfo()
}
} else {
ctx.$message('您的账号已退出,请直接登录')
ctx.$message(ctx.$t('topbar.lock-error'))
reLogin()
}
}
@ -162,7 +175,7 @@ export default defineComponent({
//
router.push({ path: route.query.redirect || '/', replace: true })
//
store.dispatch('app/setScreenCode', '')
setScreenCode('')
})
}
@ -174,7 +187,7 @@ export default defineComponent({
const reLogin = () => {
router.push('/login?redirect=' + (route.query.redirect || '/'))
// token
store.dispatch('app/clearToken')
clearToken()
}
return {

View File

@ -3,7 +3,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-11-15 09:48:29
* @LastEditTime: 2022-09-27 18:24:27
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -17,19 +17,19 @@
<el-input
class="text"
v-model="model.userName"
prefix-icon="el-icon-user-solid"
prefix-icon="User"
clearable
placeholder="用户名"
:placeholder="$t('login.username')"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
class="text"
v-model="model.password"
prefix-icon="el-icon-lock"
prefix-icon="Lock"
show-password
clearable
placeholder="密码"
:placeholder="$t('login.password')"
/>
</el-form-item>
<el-form-item>
@ -37,6 +37,7 @@
:loading="loading"
type="primary"
class="btn"
size="large"
@click="submit"
>
{{ btnText }}
@ -44,6 +45,9 @@
</el-form-item>
</el-form>
</div>
<div class="change-lang">
<change-lang />
</div>
</template>
<script>
@ -54,39 +58,57 @@ import {
toRefs,
ref,
computed,
watch,
} from 'vue'
import { Login } from '@/api/login'
import { useStore } from 'vuex'
import { useRouter, useRoute } from 'vue-router'
import ChangeLang from '@/layout/components/Topbar/ChangeLang.vue'
import useLang from '@/i18n/useLang'
import { useApp } from '@/pinia/modules/app'
export default defineComponent({
components: { ChangeLang },
name: 'login',
setup() {
const { proxy: ctx } = getCurrentInstance() // ctxvue2this
const store = useStore()
const router = useRouter()
const route = useRoute()
const { lang } = useLang()
watch(lang, () => {
state.rules = getRules()
})
const getRules = () => ({
userName: [
{
required: true,
message: ctx.$t('login.rules-username'),
trigger: 'blur',
},
],
password: [
{
required: true,
message: ctx.$t('login.rules-password'),
trigger: 'blur',
},
{
min: 6,
max: 12,
message: ctx.$t('login.rules-regpassword'),
trigger: 'blur',
},
],
})
const state = reactive({
model: {
userName: 'admin',
password: '123456',
},
rules: {
userName: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{
min: 6,
max: 12,
message: '长度在 6 到 12 个字符',
trigger: 'blur',
},
],
},
rules: getRules(),
loading: false,
btnText: computed(() => (state.loading ? '登录中...' : '登录')),
btnText: computed(() =>
state.loading ? ctx.$t('login.logining') : ctx.$t('login.login')
),
loginForm: ref(null),
submit: () => {
if (state.loading) {
@ -98,7 +120,7 @@ export default defineComponent({
const { code, data, message } = await Login(state.model)
if (+code === 200) {
ctx.$message.success({
message: '登录成功',
message: ctx.$t('login.loginsuccess'),
duration: 1000,
})
@ -112,8 +134,7 @@ export default defineComponent({
} else {
router.push('/')
}
store.dispatch('app/setToken', data)
useApp().initToken(data)
} else {
ctx.$message.error(message)
}
@ -144,6 +165,20 @@ export default defineComponent({
padding: 0 24px;
box-sizing: border-box;
margin: 160px auto 0;
:deep {
.el-input__wrapper {
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.1) inset;
background: rgba(0, 0, 0, 0.1);
}
.el-input-group--append > .el-input__wrapper {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.el-input-group--prepend > .el-input__wrapper {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
.title {
color: #fff;
text-align: center;
@ -153,8 +188,6 @@ export default defineComponent({
.text {
font-size: 16px;
:deep(.el-input__inner) {
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(0, 0, 0, 0.1);
color: #fff;
height: 48px;
line-height: 48px;
@ -168,4 +201,20 @@ export default defineComponent({
}
}
}
.change-lang {
position: fixed;
right: 20px;
top: 20px;
:deep {
.change-lang {
height: 24px;
&:hover {
background: none;
}
.icon {
color: #fff;
}
}
}
}
</style>

View File

@ -3,7 +3,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 18:10:39
* @LastEditTime: 2022-09-24 18:16:03
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -16,111 +16,4 @@
<br />
需要配置路由增加属性hidden: true注意不是在meta中增加该属性而是跟meta同级
</div>
<hr />
<div style="width: 400px">
<el-select-tree
placeholder="请选择角色"
v-model="roles"
:multiple="true"
:data="treeData"
:tree-props="treeProps"
@change="handleChange"
/>
</div>
</template>
<script>
import { defineComponent, reactive, toRefs } from 'vue'
export default defineComponent({
setup() {
const state = reactive({
roles: ['11', '311'],
treeData: [
{
id: '1',
label: '一级 1',
children: [
{
id: '11',
label: '二级 1-1',
children: [
{
id: '111',
label: '三级 1-1-1',
},
],
},
],
},
{
id: '2',
label: '一级 2',
children: [
{
id: '21',
label: '二级 2-1',
children: [
{
id: '211',
label: '三级 2-1-1',
},
],
},
{
id: '22',
label: '二级 2-2',
children: [
{
id: '221',
label: '三级 2-2-1',
},
],
},
],
},
{
id: '3',
label: '一级 3',
children: [
{
id: '31',
label: '二级 3-1',
children: [
{
id: '311',
label: '三级 3-1-1',
},
],
},
{
id: '32',
label: '二级 3-2',
children: [
{
id: '321',
label: '三级 3-2-1',
},
],
},
],
},
],
treeProps: {
props: {
children: 'children',
label: 'label',
},
},
handleChange(v) {
console.log('你选择了:', v)
},
})
return {
...toRefs(state),
}
},
})
</script>

View File

@ -1,9 +1,11 @@
<template>
<h2>
当前用户角色:{{
$store.state.account.userinfo && $store.state.account.userinfo.role
}}
</h2>
<h2>当前用户角色:{{ userinfo && userinfo.role }}</h2>
<h4><mark>刷新页面可切换随机角色</mark></h4>
<router-link to="/test/noauth">点击进入只有admin才能访问的页面</router-link>
</template>
<script setup>
import { useAccount } from '@/pinia/modules/account'
import { storeToRefs } from 'pinia'
const { userinfo } = storeToRefs(useAccount())
</script>

View File

@ -1,57 +1,75 @@
<template>
<pro-table
ref="table"
title="列表"
:title="$t('test/list.title')"
:request="getList"
:columns="columns"
:search="searchConfig"
:pagination="paginationConfig"
@selectionChange="handleSelectionChange"
>
<!-- 工具栏 -->
<template #toolbar>
<el-button type="primary" icon="el-icon-delete" @click="batchDelete">
批量删除
<el-button type="primary" icon="Delete" @click="batchDelete">
{{ $t('test/list.batchDelete') }}
</el-button>
<el-button
type="primary"
icon="el-icon-plus"
@click="$router.push('/test/add')"
>
添加一条
<el-button type="primary" icon="Plus" @click="$router.push('/test/add')">
{{ $t('test/list.add') }}
</el-button>
<el-button type="primary" icon="el-icon-refresh" @click="refresh">
刷新
<el-button type="primary" icon="Refresh" @click="refresh">
{{ $t('test/list.refresh') }}
</el-button>
</template>
<template #status="{row}">
<el-tag :type="row.status === 1 ? 'success' : 'error'">
{{ row.status === 1 ? $t('public.enabled') : $t('public.disabled') }}
</el-tag>
</template>
<template #operate="scope">
<el-button
size="mini"
size="small"
type="primary"
@click="$router.push(`/test/edit/${scope.row.id}`)"
>
编辑
{{ $t('public.edit') }}
</el-button>
<el-button size="small" type="danger">
{{ $t('public.delete') }}
</el-button>
<el-button size="mini" type="danger">删除</el-button>
</template>
</pro-table>
</template>
<script>
import { defineComponent, reactive, ref, toRefs } from 'vue'
import { getUsers } from '@/api/test'
export default defineComponent({
name: 'testList',
setup() {
// const { proxy } = getCurrentInstance()
const state = reactive({
// el-table-column
columns: [
{ type: 'selection' },
{ label: '序号', type: 'index' },
{ label: '名称', prop: 'nickName', sortable: true, width: 180 },
{ label: '邮箱', prop: 'userEmail' },
{ type: 'selection', width: 56 },
{ label: 'test/list.index', type: 'index', width: 80 },
{
label: '操作',
label: 'test/list.name',
prop: 'nickName',
sortable: true,
width: 180,
},
{
label: 'test/list.email',
prop: 'userEmail',
minWidth: 200,
},
{
label: 'public.status',
tdSlot: 'status',
width: 180,
},
{
label: 'public.operate',
width: 180,
align: 'center',
tdSlot: 'operate', //
@ -60,145 +78,141 @@ export default defineComponent({
//
searchConfig: {
labelWidth: '90px', //
inputWidth: '360px', //
inputWidth: '400px', //
fields: [
{
type: 'text',
label: '账户名称',
label: 'test/list.name',
name: 'nickName',
defaultValue: 'abc',
},
{
type: 'textarea',
label: '描述',
name: 'description',
},
{
label: '状态',
label: 'public.status',
name: 'status',
type: 'select',
defaultValue: 1,
options: [
{
name: '已发布',
name: 'test/list.publish',
value: 1,
},
{
name: '未发布',
name: 'test/list.nopublish',
value: 0,
},
],
},
{
label: '性别',
label: 'test/list.gender',
name: 'sex',
type: 'radio',
options: [
{
name: '',
name: 'public.male',
value: 1,
},
{
name: '',
name: 'public.female',
value: 0,
},
],
},
{
label: '城市',
label: 'test/list.city',
name: 'city',
type: 'radio-button',
options: [
{
name: '北京',
name: 'test/list.bj',
value: 'bj',
},
{
name: '上海',
name: 'test/list.sh',
value: 'sh',
},
{
name: '广州',
name: 'test/list.gz',
value: 'gz',
},
{
name: '深圳',
name: 'test/list.sz',
value: 'sz',
},
],
},
{
label: '爱好',
label: 'test/list.hobby',
name: 'hobby',
type: 'checkbox',
defaultValue: ['吃饭'],
defaultValue: ['eat'],
options: [
{
name: '吃饭',
value: '吃饭',
name: 'test/list.eat',
value: 'eat',
},
{
name: '睡觉',
value: '睡觉',
name: 'test/list.sleep',
value: 'sleep',
},
{
name: '打豆豆',
value: '打豆豆',
name: 'test/list.bit',
value: 'bit',
},
],
// transform: (val) => val.join(","),
},
{
label: '水果',
label: 'test/list.fruit',
name: 'fruit',
type: 'checkbox-button',
options: [
{
name: '苹果',
value: '苹果',
name: 'test/list.apple',
value: 'apple',
},
{
name: '香蕉',
value: '香蕉',
name: 'test/list.banana',
value: 'banana',
},
{
name: '橘子',
value: '橘子',
name: 'test/list.orange',
value: 'orange',
},
{
name: '葡萄',
value: '葡萄',
name: 'test/list.grape',
value: 'grape',
},
],
transform: val => val.join(','),
},
{
label: '日期',
label: 'test/list.date',
name: 'date',
type: 'date',
},
{
label: '时间',
label: 'test/list.time',
name: 'datetime',
type: 'datetime',
defaultValue: '2020-10-10 8:00:00',
},
{
label: '日期范围',
label: 'test/list.daterange',
name: 'daterange',
type: 'daterange',
trueNames: ['startDate', 'endDate'],
style: { width: '400px' },
},
{
label: '时间范围',
label: 'test/list.timerange',
name: 'datetimerange',
type: 'datetimerange',
trueNames: ['startTime', 'endTime'],
style: { width: '360px' },
style: { width: '400px' },
defaultValue: ['2020-10-10 9:00:00', '2020-10-11 18:30:00'],
},
{
label: '数量',
label: 'test/list.num',
name: 'num',
type: 'number',
min: 0,
@ -207,12 +221,12 @@ export default defineComponent({
],
},
//
paginationConfig: {
layout: 'total, prev, pager, next, sizes', //
pageSize: 5, //
pageSizes: [5, 10, 20, 50],
style: { textAlign: 'left' },
},
// paginationConfig: {
// layout: 'total, prev, pager, next, sizes', //
// pageSize: 10, //
// pageSizes: [5, 10, 20, 50],
// style: { 'justify-content': 'flex-end' },
// },
selectedItems: [],
batchDelete() {
console.log(state.selectedItems)
@ -225,28 +239,7 @@ export default defineComponent({
async getList(params) {
console.log(params)
// params
const { data } = await new Promise(rs => {
setTimeout(() => {
rs({
code: 200,
data: {
list: [
{
id: 1,
nickName: 'zhangsan',
userEmail: 'zhangsan@xx.com',
},
{
id: 2,
nickName: 'lisi',
userEmail: 'lisi@xx.com',
},
],
total: 100,
},
})
}, 3000)
})
const { data } = await getUsers(params)
// datatotal
return {

View File

@ -14,7 +14,7 @@
* @version:
* @Date: 2021-04-20 11:06:21
* @LastEditors: huzhushan@126.com
* @LastEditTime: 2021-09-18 15:09:15
* @LastEditTime: 2022-09-24 14:33:17
* @Author: huzhushan@126.com
* @HomePage: https://huzhushan.gitee.io/vue3-element-admin
* @Github: https://github.com/huzhushan/vue3-element-admin
@ -61,7 +61,9 @@ export default env => {
preprocessorOptions: {
scss: {
// 全局变量
additionalData: '@import "./src/assets/style/global-variables.scss";',
// additionalData: '@import "./src/assets/style/global-variables.scss";',
// element-plus升级到v2需要改成以下写法
additionalData: `@use "./src/assets/style/global-variables.scss" as *;`,
},
},
},
@ -71,6 +73,7 @@ export default env => {
},
},
server: {
port: 3001,
open: true,
proxy: {
'/api': {