mirror of
https://github.com/zclzone/vue-naive-admin.git
synced 2025-12-28 20:10:22 +08:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83b42bf6b8 | ||
|
|
fd08d25ccf | ||
|
|
76c3f0b64c | ||
|
|
a1db8273f5 | ||
|
|
f5ab04112f | ||
|
|
805b2e066f | ||
|
|
6979b245a9 | ||
|
|
dff8862c75 | ||
|
|
1da5e8d573 | ||
|
|
7f97dd2f5a | ||
|
|
1f69f07100 | ||
|
|
f97beeb54b | ||
|
|
57bc68e7b0 | ||
|
|
90aa54d4a4 | ||
|
|
7564f115d6 | ||
|
|
8d3753a80e | ||
|
|
a816028560 | ||
|
|
acde2c1004 | ||
|
|
cb5dd34e17 | ||
|
|
73c82520ca | ||
|
|
e465ee50bf | ||
|
|
2be3f095aa | ||
|
|
26ecafffdc | ||
|
|
7150d93394 |
42
.cz-config.js
Normal file
42
.cz-config.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
module.exports = {
|
||||||
|
types: [
|
||||||
|
{ value: 'feat', name:'feat: 新增功能' },
|
||||||
|
{ value: 'fix', name:'fix: 修复bug' },
|
||||||
|
{ value: 'docs', name:'docs: 文档变更' },
|
||||||
|
{ value: 'style', name:'style: 代码格式(不影响功能,例如空格、分号等格式修正)' },
|
||||||
|
{ value: 'refactor', name:'refactor: 代码重构(不包括 bug 修复、功能新增)' },
|
||||||
|
{ value: 'perf', name:'perf: 性能优化' },
|
||||||
|
{ value: 'test', name:'test: 添加、修改测试用例' },
|
||||||
|
{ value: 'build', name:'build: 构建流程、外部依赖变更(如升级 npm 包、修改 脚手架 配置等)' },
|
||||||
|
{ value: 'ci', name:'ci: 修改 CI 配置、脚本' },
|
||||||
|
{ value: 'chore', name:'chore: 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)' },
|
||||||
|
{ value: 'revert', name:'revert: 回滚 commit' },
|
||||||
|
],
|
||||||
|
scopes: [
|
||||||
|
['custom', '自定义3'],
|
||||||
|
['projects', '项目搭建'],
|
||||||
|
['components', '组件相关'],
|
||||||
|
['utils', 'utils 相关'],
|
||||||
|
['styles', '样式相关'],
|
||||||
|
['deps', '项目依赖'],
|
||||||
|
['other', '其他修改'],
|
||||||
|
].map(([value, description]) => {
|
||||||
|
return {
|
||||||
|
value,
|
||||||
|
name: `${value.padEnd(30)} (${description})`
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
messages: {
|
||||||
|
type: '确保本次提交遵循 Angular 规范!选择你要提交的类型:\n',
|
||||||
|
scope: '选择一个 scope(可选):',
|
||||||
|
customScope: '请输入自定义的 scope:',
|
||||||
|
subject: '填写简短精炼的变更描述:',
|
||||||
|
body: '填写更加详细的变更描述(可选)。使用 "|" 换行:',
|
||||||
|
breaking: '列举非兼容性重大的变更(可选):',
|
||||||
|
footer: '列举出所有变更的 Issues Closed(可选)。 例如: #31, #34:',
|
||||||
|
confirmCommit: '确认提交?'
|
||||||
|
},
|
||||||
|
allowBreakingChanges: ['feat', 'fix'],
|
||||||
|
subjectLimit: 100,
|
||||||
|
breaklineChar: '|'
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ module.exports = {
|
|||||||
root: true,
|
root: true,
|
||||||
extends: ['plugin:vue/vue3-recommended', 'plugin:prettier/recommended'],
|
extends: ['plugin:vue/vue3-recommended', 'plugin:prettier/recommended'],
|
||||||
rules: {
|
rules: {
|
||||||
'prettier/prettier': 'warn',
|
'prettier/prettier': 'error',
|
||||||
'vue/valid-template-root': 'off',
|
'vue/valid-template-root': 'off',
|
||||||
'vue/no-multiple-template-root': 'off',
|
'vue/no-multiple-template-root': 'off',
|
||||||
'vue/multi-word-component-names': [
|
'vue/multi-word-component-names': [
|
||||||
|
|||||||
36
.husky/_/husky.sh
Normal file
36
.husky/_/husky.sh
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
if [ -z "$husky_skip_init" ]; then
|
||||||
|
debug () {
|
||||||
|
if [ "$HUSKY_DEBUG" = "1" ]; then
|
||||||
|
echo "husky (debug) - $1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly hook_name="$(basename -- "$0")"
|
||||||
|
debug "starting $hook_name..."
|
||||||
|
|
||||||
|
if [ "$HUSKY" = "0" ]; then
|
||||||
|
debug "HUSKY env variable is set to 0, skipping hook"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f ~/.huskyrc ]; then
|
||||||
|
debug "sourcing ~/.huskyrc"
|
||||||
|
. ~/.huskyrc
|
||||||
|
fi
|
||||||
|
|
||||||
|
readonly husky_skip_init=1
|
||||||
|
export husky_skip_init
|
||||||
|
sh -e "$0" "$@"
|
||||||
|
exitCode="$?"
|
||||||
|
|
||||||
|
if [ $exitCode != 0 ]; then
|
||||||
|
echo "husky - $hook_name hook exited with code $exitCode (error)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $exitCode = 127 ]; then
|
||||||
|
echo "husky - command not found in PATH=$PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit $exitCode
|
||||||
|
fi
|
||||||
4
.husky/commit-msg
Normal file
4
.husky/commit-msg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npx --no-install commitlint --edit "$1"
|
||||||
4
.husky/pre-commit
Normal file
4
.husky/pre-commit
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npm run lint
|
||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,8 +1,5 @@
|
|||||||
{
|
{
|
||||||
"files.eol": "\n",
|
"files.eol": "\n",
|
||||||
"path-intellisense.mappings": {
|
|
||||||
"@/": "${workspaceRoot}/src"
|
|
||||||
},
|
|
||||||
"editor.formatOnSave": false,
|
"editor.formatOnSave": false,
|
||||||
"eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"],
|
"eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"],
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
### 简介
|
### 简介
|
||||||
|
|
||||||
[Vue Naive Admin](https://github.com/zclzone/vue-naive-admin),一个基于 Vue3.0、Vite、Naive UI 的后台管理模板,相较于其他比较流行的后台管理模板,此项目相对简洁、轻量,学习成本非常低,对新手极其友好。不过麻雀虽小五脏俱全,权限、Mock、菜单、axios 封装、pinia、项目配置、样式配置、环境配置,以及一些经常用的基础组件封装等等这些该有的都有,非常适用于中小型项目或者个人项目,也可此模板进行二次封装改造用于大型项目。
|
[Vue Naive Admin](https://github.com/zclzone/vue-naive-admin)是一个 **完全开源免费且允许商用** 的后台管理模板,基于 Vue3、Vite2、Pinia 和 Naive UI等前端最新技术栈。相较于其他比较流行的后台管理模板,此项目相对简洁、轻量,学习成本非常低。麻雀虽小,五脏俱全,权限、Mock、菜单、axios 封装、pinia、项目配置、样式配置、环境配置,以及一些经常用的基础组件封装等等这些该有的都有,非常适合中小型项目或者个人项目。
|
||||||
|
|
||||||
### 为什么要开发这个模板
|
### 为什么要开发这个模板
|
||||||
|
|
||||||
@@ -113,10 +113,10 @@ npm run preview
|
|||||||
- `mod` 不确定分类的修改
|
- `mod` 不确定分类的修改
|
||||||
- `release` 发布
|
- `release` 发布
|
||||||
|
|
||||||
|
### 入群交流
|
||||||
|
|
||||||
|
<p>
|
||||||
<p align="center">
|
<img src="https://assets.qszone.com/image/入群.png" />
|
||||||
<img src="https://assets.qszone.com/image/Snipaste_2022-06-23_19-26-26.png" />
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createHtmlPlugin } from 'vite-plugin-html'
|
import { createHtmlPlugin } from 'vite-plugin-html'
|
||||||
|
|
||||||
export function configHtmlPlugin(viteEnv, isBuild) {
|
export function configHtmlPlugin(viteEnv, isBuild) {
|
||||||
const { VITE_APP_TITLE, VITE_PUBLIC_PATH } = viteEnv
|
const { VITE_APP_TITLE } = viteEnv
|
||||||
|
|
||||||
const htmlPlugin = createHtmlPlugin({
|
const htmlPlugin = createHtmlPlugin({
|
||||||
minify: isBuild,
|
minify: isBuild,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import vue from '@vitejs/plugin-vue'
|
|||||||
* * 扩展setup插件,支持在script标签中使用name属性
|
* * 扩展setup插件,支持在script标签中使用name属性
|
||||||
* usage: <script setup name="MyComp"></script>
|
* usage: <script setup name="MyComp"></script>
|
||||||
*/
|
*/
|
||||||
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
|
import vueSetupExtend from 'vite-plugin-vue-setup-extend-plus'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* * unocss插件,原子css
|
* * unocss插件,原子css
|
||||||
@@ -20,7 +20,7 @@ import { configMockPlugin } from './mock'
|
|||||||
import unplugin from './unplugin'
|
import unplugin from './unplugin'
|
||||||
|
|
||||||
export function createVitePlugins(viteEnv, isBuild) {
|
export function createVitePlugins(viteEnv, isBuild) {
|
||||||
const plugins = [vue(), VueSetupExtend(), ...unplugin, configHtmlPlugin(viteEnv, isBuild), Unocss()]
|
const plugins = [vue(), vueSetupExtend(), ...unplugin, configHtmlPlugin(viteEnv, isBuild), Unocss()]
|
||||||
|
|
||||||
if (viteEnv?.VITE_APP_USE_MOCK) {
|
if (viteEnv?.VITE_APP_USE_MOCK) {
|
||||||
plugins.push(configMockPlugin(isBuild))
|
plugins.push(configMockPlugin(isBuild))
|
||||||
|
|||||||
@@ -2,12 +2,11 @@ import { viteMockServe } from 'vite-plugin-mock'
|
|||||||
|
|
||||||
export function configMockPlugin(isBuild) {
|
export function configMockPlugin(isBuild) {
|
||||||
return viteMockServe({
|
return viteMockServe({
|
||||||
ignore: /^\_/,
|
mockPath: 'mock/modules',
|
||||||
mockPath: 'mock',
|
|
||||||
localEnabled: !isBuild,
|
localEnabled: !isBuild,
|
||||||
prodEnabled: isBuild,
|
prodEnabled: isBuild,
|
||||||
injectCode: `
|
injectCode: `
|
||||||
import { setupProdMockServer } from '../mock/_create-prod-server';
|
import { setupProdMockServer } from '../mock';
|
||||||
setupProdMockServer();
|
setupProdMockServer();
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
|
|||||||
4
commitlint.config.js
Normal file
4
commitlint.config.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
ignores: [(commit) => commit.includes('init')],
|
||||||
|
extends: ['@commitlint/config-conventional'],
|
||||||
|
}
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
|
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
|
||||||
|
|
||||||
const modules = import.meta.globEager('./**/*.js')
|
const modules = import.meta.globEager('./modules/*.js')
|
||||||
const mockModules = []
|
const mockModules = []
|
||||||
Object.keys(modules).forEach((key) => {
|
Object.keys(modules).forEach((key) => {
|
||||||
if (key.includes('/_')) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mockModules.push(...modules[key].default)
|
mockModules.push(...modules[key].default)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { resolveToken } from '../_utils'
|
import { resolveToken } from '../utils'
|
||||||
|
|
||||||
const token = {
|
const token = {
|
||||||
admin: 'admin',
|
admin: 'admin',
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { resolveToken } from '../_utils'
|
import { resolveToken } from '../utils'
|
||||||
|
|
||||||
const users = {
|
const users = {
|
||||||
admin: {
|
admin: {
|
||||||
19
package.json
19
package.json
@@ -8,7 +8,9 @@
|
|||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"build:test": "vite build --mode test",
|
"build:test": "vite build --mode test",
|
||||||
"build:github": "vite build --mode github && esno ./build/script",
|
"build:github": "vite build --mode github && esno ./build/script",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"prepare": "husky install",
|
||||||
|
"cz": "cz"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vueuse/core": "^8.4.2",
|
"@vueuse/core": "^8.4.2",
|
||||||
@@ -21,11 +23,16 @@
|
|||||||
"vue-router": "^4.0.15"
|
"vue-router": "^4.0.15"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@commitlint/cli": "^17.0.3",
|
||||||
|
"@commitlint/config-conventional": "^17.0.3",
|
||||||
"@iconify/json": "^2.1.63",
|
"@iconify/json": "^2.1.63",
|
||||||
"@iconify/vue": "^3.2.1",
|
"@iconify/vue": "^3.2.1",
|
||||||
"@vitejs/plugin-vue": "^1.10.2",
|
"@vitejs/plugin-vue": "^1.10.2",
|
||||||
"@vue/compiler-sfc": "^3.2.31",
|
"@vue/compiler-sfc": "^3.2.31",
|
||||||
"chalk": "^5.0.1",
|
"chalk": "^5.0.1",
|
||||||
|
"commitizen": "^4.2.4",
|
||||||
|
"cz-conventional-changelog": "^3.3.0",
|
||||||
|
"cz-customizable": "^6.9.0",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
"eslint": "^8.12.0",
|
"eslint": "^8.12.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
@@ -33,17 +40,23 @@
|
|||||||
"eslint-plugin-vue": "^8.5.0",
|
"eslint-plugin-vue": "^8.5.0",
|
||||||
"esno": "^0.13.0",
|
"esno": "^0.13.0",
|
||||||
"fs-extra": "^10.0.1",
|
"fs-extra": "^10.0.1",
|
||||||
|
"husky": "^8.0.1",
|
||||||
"naive-ui": "^2.30.3",
|
"naive-ui": "^2.30.3",
|
||||||
"prettier": "^2.6.1",
|
"prettier": "^2.6.1",
|
||||||
"rollup-plugin-visualizer": "^5.6.0",
|
"rollup-plugin-visualizer": "^5.6.0",
|
||||||
"sass": "^1.49.10",
|
"sass": "^1.49.10",
|
||||||
"unocss": "^0.38.2",
|
"unocss": "^0.43.2",
|
||||||
"unplugin-auto-import": "^0.8.8",
|
"unplugin-auto-import": "^0.8.8",
|
||||||
"unplugin-icons": "^0.14.1",
|
"unplugin-icons": "^0.14.1",
|
||||||
"unplugin-vue-components": "^0.17.21",
|
"unplugin-vue-components": "^0.17.21",
|
||||||
"vite": "^2.9.9",
|
"vite": "^2.9.9",
|
||||||
"vite-plugin-html": "^3.2.0",
|
"vite-plugin-html": "^3.2.0",
|
||||||
"vite-plugin-mock": "^2.9.6",
|
"vite-plugin-mock": "^2.9.6",
|
||||||
"vite-plugin-vue-setup-extend": "^0.3.0"
|
"vite-plugin-vue-setup-extend-plus": "^0.1.0"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"commitizen": {
|
||||||
|
"path": "node_modules/cz-customizable"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1569
pnpm-lock.yaml
generated
1569
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -3,4 +3,5 @@ module.exports = {
|
|||||||
singleQuote: true,
|
singleQuote: true,
|
||||||
semi: false,
|
semi: false,
|
||||||
endOfLine: 'lf',
|
endOfLine: 'lf',
|
||||||
|
bracketSameLine: true,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { defAxios as request } from '@/utils/http'
|
import { defAxios as request } from '@/utils/http'
|
||||||
|
|
||||||
export function getPosts(data = {}) {
|
export function getPosts(params = {}) {
|
||||||
return request({
|
return request({
|
||||||
url: '/posts',
|
url: '/posts',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
data,
|
params,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { defAxios as request } from '@/utils/http'
|
import { defAxios as request } from '@/utils/http'
|
||||||
|
|
||||||
export function getUsers(data = {}) {
|
export function getUsers(params = {}) {
|
||||||
return request({
|
return request({
|
||||||
url: '/users',
|
url: '/users',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
data,
|
params,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="wrapper" class="tags-wrapper" @mousewheel.prevent="handleMouseWheel">
|
<div ref="wrapper" class="wrapper" @mousewheel.prevent="handleMouseWheel">
|
||||||
<template v-if="showArrow && isOverflow">
|
<template v-if="showArrow && isOverflow">
|
||||||
<div class="left" @click="handleMouseWheel({ wheelDelta: 50 })">
|
<div class="left" @click="handleMouseWheel({ wheelDelta: 120 })">
|
||||||
<icon-ic:baseline-keyboard-arrow-left />
|
<icon-ic:baseline-keyboard-arrow-left />
|
||||||
</div>
|
</div>
|
||||||
<div class="right" @click="handleMouseWheel({ wheelDelta: -50 })">
|
<div class="right" @click="handleMouseWheel({ wheelDelta: -120 })">
|
||||||
<icon-ic:baseline-keyboard-arrow-right />
|
<icon-ic:baseline-keyboard-arrow-right />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
ref="content"
|
ref="content"
|
||||||
class="tags-content"
|
class="content"
|
||||||
:class="{ overflow: isOverflow && showArrow }"
|
:class="{ overflow: isOverflow && showArrow }"
|
||||||
:style="{
|
:style="{
|
||||||
height: height + 'px',
|
|
||||||
transform: `translateX(${translateX}px)`,
|
transform: `translateX(${translateX}px)`,
|
||||||
}"
|
}">
|
||||||
>
|
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -25,37 +23,26 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { debounce } from '@/utils'
|
import { debounce } from '@/utils'
|
||||||
import { isNullOrUndef } from '@/utils/is'
|
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
height: {
|
|
||||||
type: Number,
|
|
||||||
default: 50,
|
|
||||||
},
|
|
||||||
showArrow: {
|
showArrow: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
refreshIsOverflow()
|
|
||||||
})
|
|
||||||
|
|
||||||
const translateX = ref(0)
|
const translateX = ref(0)
|
||||||
const content = ref(null)
|
const content = ref(null)
|
||||||
const wrapper = ref(null)
|
const wrapper = ref(null)
|
||||||
const isOverflow = ref(false)
|
const isOverflow = ref(false)
|
||||||
|
|
||||||
function refreshIsOverflow(isIncrease) {
|
const refreshIsOverflow = debounce(() => {
|
||||||
isOverflow.value = content.value.offsetWidth > wrapper.value.offsetWidth
|
const wrapperWidth = wrapper.value.offsetWidth
|
||||||
if (isNullOrUndef(isIncrease)) return
|
const contentWidth = content.value.offsetWidth
|
||||||
if (isOverflow.value) {
|
isOverflow.value = contentWidth > wrapperWidth
|
||||||
handleMouseWheel({ wheelDelta: isIncrease ? -100 : 100 })
|
resetTranslateX(wrapperWidth, contentWidth)
|
||||||
} else if (!isIncrease && translateX.value < 0) {
|
}, 200)
|
||||||
handleMouseWheel({ wheelDelta: 100 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function handleMouseWheel(e) {
|
function handleMouseWheel(e) {
|
||||||
const { wheelDelta } = e
|
const { wheelDelta } = e
|
||||||
const wrapperWidth = wrapper.value.offsetWidth
|
const wrapperWidth = wrapper.value.offsetWidth
|
||||||
@@ -66,15 +53,15 @@ function handleMouseWheel(e) {
|
|||||||
* @wrapperWidth 容器的宽度
|
* @wrapperWidth 容器的宽度
|
||||||
* @contentWidth 内容的宽度
|
* @contentWidth 内容的宽度
|
||||||
*/
|
*/
|
||||||
if (wheelDelta < 0 && -translateX.value > contentWidth - wrapperWidth + 10) {
|
if (wheelDelta < 0) {
|
||||||
return
|
if (wrapperWidth > contentWidth && translateX.value < -10) return
|
||||||
|
if (wrapperWidth <= contentWidth && contentWidth + translateX.value - wrapperWidth < -10) return
|
||||||
}
|
}
|
||||||
if (wheelDelta > 0 && translateX.value > 10) {
|
if (wheelDelta > 0 && translateX.value > 10) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
translateX.value += wheelDelta
|
translateX.value += wheelDelta
|
||||||
|
|
||||||
resetTranslateX(wrapperWidth, contentWidth)
|
resetTranslateX(wrapperWidth, contentWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,20 +75,29 @@ const resetTranslateX = debounce(function (wrapperWidth, contentWidth) {
|
|||||||
}
|
}
|
||||||
}, 200)
|
}, 200)
|
||||||
|
|
||||||
defineExpose({
|
const observer = new MutationObserver(refreshIsOverflow)
|
||||||
refreshIsOverflow,
|
onMounted(() => {
|
||||||
|
refreshIsOverflow()
|
||||||
|
|
||||||
|
window.addEventListener('resize', refreshIsOverflow)
|
||||||
|
// 监听内容宽度刷新是否超出
|
||||||
|
observer.observe(content.value, { childList: true })
|
||||||
|
})
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('resize', refreshIsOverflow)
|
||||||
|
observer.disconnect()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.tags-wrapper {
|
.wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 9;
|
z-index: 9;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
.tags-content {
|
position: relative;
|
||||||
|
.content {
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-breadcrumb>
|
<n-breadcrumb>
|
||||||
<n-breadcrumb-item v-for="item in route.matched" :key="item.path" @click="handleBreadClick(item.path)">
|
<n-breadcrumb-item v-for="item in route.matched" :key="item.path" @click="handleBreadClick(item.path)">
|
||||||
|
<component :is="renderIcon(item.meta?.icon, { size: 16 })" v-if="item.meta?.icon" />
|
||||||
{{ item.meta.title }}
|
{{ item.meta.title }}
|
||||||
</n-breadcrumb-item>
|
</n-breadcrumb-item>
|
||||||
</n-breadcrumb>
|
</n-breadcrumb>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { renderIcon } from '@/utils/icon'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,7 @@
|
|||||||
:collapsed-width="64"
|
:collapsed-width="64"
|
||||||
:options="menuOptions"
|
:options="menuOptions"
|
||||||
:value="(currentRoute.meta && currentRoute.meta.activeMenu) || currentRoute.name"
|
:value="(currentRoute.meta && currentRoute.meta.activeMenu) || currentRoute.name"
|
||||||
@update:value="handleMenuSelect"
|
@update:value="handleMenuSelect" />
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|||||||
@@ -6,8 +6,7 @@
|
|||||||
:y="y"
|
:y="y"
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
@clickoutside="handleHideDropdown"
|
@clickoutside="handleHideDropdown"
|
||||||
@select="handleSelect"
|
@select="handleSelect" />
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<ScrollX ref="scrollX" :height="useTheme.tags.height">
|
<ScrollX :class="`h-${useTheme.tags.height}`">
|
||||||
<n-tag
|
<n-tag
|
||||||
v-for="tag in tagsStore.tags"
|
v-for="tag in tagsStore.tags"
|
||||||
:key="tag.path"
|
:key="tag.path"
|
||||||
@@ -7,8 +7,7 @@
|
|||||||
:closable="tagsStore.tags.length > 1"
|
:closable="tagsStore.tags.length > 1"
|
||||||
@click="handleTagClick(tag.path)"
|
@click="handleTagClick(tag.path)"
|
||||||
@close.stop="tagsStore.removeTag(tag.path)"
|
@close.stop="tagsStore.removeTag(tag.path)"
|
||||||
@contextmenu.prevent="handleContextMenu($event, tag)"
|
@contextmenu.prevent="handleContextMenu($event, tag)">
|
||||||
>
|
|
||||||
{{ tag.title }}
|
{{ tag.title }}
|
||||||
</n-tag>
|
</n-tag>
|
||||||
</ScrollX>
|
</ScrollX>
|
||||||
@@ -17,8 +16,7 @@
|
|||||||
v-model:show="contextMenuOption.show"
|
v-model:show="contextMenuOption.show"
|
||||||
:current-path="contextMenuOption.currentPath"
|
:current-path="contextMenuOption.currentPath"
|
||||||
:x="contextMenuOption.x"
|
:x="contextMenuOption.x"
|
||||||
:y="contextMenuOption.y"
|
:y="contextMenuOption.y" />
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup name="Tags">
|
<script setup name="Tags">
|
||||||
@@ -49,15 +47,6 @@ watch(
|
|||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
const scrollX = ref(null)
|
|
||||||
watch(
|
|
||||||
() => tagsStore.tags,
|
|
||||||
async (newVal, oldVal) => {
|
|
||||||
await nextTick()
|
|
||||||
scrollX.value?.refreshIsOverflow(newVal.length > oldVal.length)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const handleTagClick = (path) => {
|
const handleTagClick = (path) => {
|
||||||
tagsStore.setActiveTag(path)
|
tagsStore.setActiveTag(path)
|
||||||
router.push(path)
|
router.push(path)
|
||||||
|
|||||||
@@ -6,8 +6,7 @@
|
|||||||
:collapsed-width="64"
|
:collapsed-width="64"
|
||||||
:width="220"
|
:width="220"
|
||||||
:native-scrollbar="false"
|
:native-scrollbar="false"
|
||||||
:collapsed="appStore.collapsed"
|
:collapsed="appStore.collapsed">
|
||||||
>
|
|
||||||
<SideBar />
|
<SideBar />
|
||||||
</n-layout-sider>
|
</n-layout-sider>
|
||||||
<n-layout>
|
<n-layout>
|
||||||
@@ -22,8 +21,7 @@
|
|||||||
:style="{
|
:style="{
|
||||||
height: `calc(100% - ${useTheme.tags.visible ? useTheme.tags.height : 0}px)`,
|
height: `calc(100% - ${useTheme.tags.visible ? useTheme.tags.height : 0}px)`,
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
}"
|
}" />
|
||||||
/>
|
|
||||||
</n-layout>
|
</n-layout>
|
||||||
</n-layout>
|
</n-layout>
|
||||||
</n-layout>
|
</n-layout>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export default [
|
|||||||
meta: {
|
meta: {
|
||||||
title: '组件示例',
|
title: '组件示例',
|
||||||
role: ['admin'],
|
role: ['admin'],
|
||||||
|
icon: 'mdi:menu',
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { createSessionStorage } from '@/utils/cache'
|
import { sStorage } from '@/utils/cache'
|
||||||
|
|
||||||
export const tagsSS = createSessionStorage({ prefixKey: 'tag_' })
|
export const activeTag = sStorage.get('activeTag')
|
||||||
export const activeTag = tagsSS.get('activeTag')
|
export const tags = sStorage.get('tags')
|
||||||
export const tags = tagsSS.get('tags')
|
|
||||||
|
|
||||||
export const WITHOUT_TAG_PATHS = ['/404', '/login', '/redirect']
|
export const WITHOUT_TAG_PATHS = ['/404', '/login', '/redirect']
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { tagsSS, activeTag, tags, WITHOUT_TAG_PATHS } from './helpers'
|
import { activeTag, tags, WITHOUT_TAG_PATHS } from './helpers'
|
||||||
import { router } from '@/router'
|
import { router } from '@/router'
|
||||||
|
import { sStorage } from '@/utils/cache'
|
||||||
|
|
||||||
export const useTagsStore = defineStore('tag', {
|
export const useTagsStore = defineStore('tag', {
|
||||||
state() {
|
state() {
|
||||||
@@ -12,11 +13,11 @@ export const useTagsStore = defineStore('tag', {
|
|||||||
actions: {
|
actions: {
|
||||||
setActiveTag(path) {
|
setActiveTag(path) {
|
||||||
this.activeTag = path
|
this.activeTag = path
|
||||||
tagsSS.set('activeTag', path)
|
sStorage.set('activeTag', path)
|
||||||
},
|
},
|
||||||
setTags(tags) {
|
setTags(tags) {
|
||||||
this.tags = tags
|
this.tags = tags
|
||||||
tagsSS.set('tags', tags)
|
sStorage.set('tags', tags)
|
||||||
},
|
},
|
||||||
addTag(tag = {}) {
|
addTag(tag = {}) {
|
||||||
this.setActiveTag(tag.path)
|
this.setActiveTag(tag.path)
|
||||||
|
|||||||
12
src/utils/cache/index.js
vendored
12
src/utils/cache/index.js
vendored
@@ -1,15 +1,21 @@
|
|||||||
import { createWebStorage } from './web-storage'
|
import { createStorage } from './storage'
|
||||||
|
|
||||||
|
const prefixKey = 'Vue_Naive_Admin_'
|
||||||
|
|
||||||
export const createLocalStorage = function (option = {}) {
|
export const createLocalStorage = function (option = {}) {
|
||||||
return createWebStorage({
|
return createStorage({
|
||||||
prefixKey: option.prefixKey || '',
|
prefixKey: option.prefixKey || '',
|
||||||
storage: localStorage,
|
storage: localStorage,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createSessionStorage = function (option = {}) {
|
export const createSessionStorage = function (option = {}) {
|
||||||
return createWebStorage({
|
return createStorage({
|
||||||
prefixKey: option.prefixKey || '',
|
prefixKey: option.prefixKey || '',
|
||||||
storage: sessionStorage,
|
storage: sessionStorage,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const lStorage = createLocalStorage({ prefixKey })
|
||||||
|
|
||||||
|
export const sStorage = createSessionStorage({ prefixKey })
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { isNullOrUndef } from '@/utils/is'
|
import { isNullOrUndef } from '@/utils/is'
|
||||||
|
|
||||||
class WebStorage {
|
class Storage {
|
||||||
constructor(option) {
|
constructor(option) {
|
||||||
this.storage = option.storage
|
this.storage = option.storage
|
||||||
this.prefixKey = option.prefixKey
|
this.prefixKey = option.prefixKey
|
||||||
@@ -50,6 +50,6 @@ class WebStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createWebStorage({ prefixKey = '', storage = sessionStorage }) {
|
export function createStorage({ prefixKey = '', storage = sessionStorage }) {
|
||||||
return new WebStorage({ prefixKey, storage })
|
return new Storage({ prefixKey, storage })
|
||||||
}
|
}
|
||||||
@@ -52,6 +52,9 @@ export function repReject(error) {
|
|||||||
* TODO 此处可以根据后端返回的错误码自定义框架层面的错误处理
|
* TODO 此处可以根据后端返回的错误码自定义框架层面的错误处理
|
||||||
*/
|
*/
|
||||||
switch (code) {
|
switch (code) {
|
||||||
|
case 400:
|
||||||
|
message = message || '请求参数错误'
|
||||||
|
break
|
||||||
case 401:
|
case 401:
|
||||||
message = message || '登录已过期'
|
message = message || '登录已过期'
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -1,25 +1,23 @@
|
|||||||
import { createLocalStorage } from './cache'
|
import { lStorage } from './cache'
|
||||||
import { refreshToken } from '@/api/auth'
|
import { refreshToken } from '@/api/auth'
|
||||||
|
|
||||||
const TOKEN_CODE = 'access_token'
|
const TOKEN_CODE = 'access_token'
|
||||||
const DURATION = 6 * 60 * 60
|
const DURATION = 6 * 60 * 60
|
||||||
|
|
||||||
export const lsToken = createLocalStorage()
|
|
||||||
|
|
||||||
export function getToken() {
|
export function getToken() {
|
||||||
return lsToken.get(TOKEN_CODE)
|
return lStorage.get(TOKEN_CODE)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setToken(token) {
|
export function setToken(token) {
|
||||||
lsToken.set(TOKEN_CODE, token, DURATION)
|
lStorage.set(TOKEN_CODE, token, DURATION)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeToken() {
|
export function removeToken() {
|
||||||
lsToken.remove(TOKEN_CODE)
|
lStorage.remove(TOKEN_CODE)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function refreshAccessToken() {
|
export async function refreshAccessToken() {
|
||||||
const tokenItem = lsToken.getItem(TOKEN_CODE)
|
const tokenItem = lStorage.getItem(TOKEN_CODE)
|
||||||
if (!tokenItem) {
|
if (!tokenItem) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,7 @@
|
|||||||
class="w-300 flex-shrink-0 mt-10 mb-10 cursor-pointer"
|
class="w-300 flex-shrink-0 mt-10 mb-10 cursor-pointer"
|
||||||
hover:card-shadow
|
hover:card-shadow
|
||||||
title="Vue Naive Admin"
|
title="Vue Naive Admin"
|
||||||
size="small"
|
size="small">
|
||||||
>
|
|
||||||
<p op-60>一个基于 Vue3.0、Vite、Naive UI 的轻量级后台管理模板</p>
|
<p op-60>一个基于 Vue3.0、Vite、Naive UI 的轻量级后台管理模板</p>
|
||||||
</n-card>
|
</n-card>
|
||||||
<div w-300 h-0></div>
|
<div w-300 h-0></div>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="page-404">
|
<div h-full flex>
|
||||||
<n-result status="404" description="抱歉,您访问的页面不存在。">
|
<n-result m-auto status="404" description="抱歉,您访问的页面不存在。">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<img src="@/assets/images/404.png" width="500" />
|
<img src="@/assets/images/404.png" width="500" />
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<n-button strong secondary type="primary" @click="replace('/')">返回首页</n-button>
|
<n-button @click="replace('/')">返回首页</n-button>
|
||||||
</template>
|
</template>
|
||||||
</n-result>
|
</n-result>
|
||||||
</div>
|
</div>
|
||||||
@@ -14,12 +14,3 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
const { replace } = useRouter()
|
const { replace } = useRouter()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.page-404 {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div p-20>
|
<div p-24>
|
||||||
<div class="header">
|
<div h-60 pl-20 pr-20 flex items-center bg-white>
|
||||||
<input v-model="post.title" type="text" placeholder="输入文章标题..." class="title" />
|
<input
|
||||||
|
v-model="post.title"
|
||||||
|
class="flex-1 pt-15 pb-15 mr-20 text-20 font-bold color-primary"
|
||||||
|
type="text"
|
||||||
|
placeholder="输入文章标题..." />
|
||||||
<n-button type="primary" style="width: 80px" :loading="btnLoading" @click="handleSavePost">保存</n-button>
|
<n-button type="primary" style="width: 80px" :loading="btnLoading" @click="handleSavePost">保存</n-button>
|
||||||
</div>
|
</div>
|
||||||
<MdEditor v-model="post.content" style="height: calc(100vh - 210px)" />
|
<MdEditor v-model="post.content" style="height: calc(100vh - 220px)" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -37,21 +41,3 @@ function handleSavePost(e) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.header {
|
|
||||||
background-color: #fff;
|
|
||||||
height: 60px;
|
|
||||||
padding: 0 20px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
.title {
|
|
||||||
flex: 1;
|
|
||||||
padding: 15px 0;
|
|
||||||
margin-right: 20px;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--primaryColor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div p24>
|
<div p-24>
|
||||||
<div class="action-btns">
|
<div flex>
|
||||||
<n-button size="small" type="primary" @click="handleCreate">新建文章</n-button>
|
<n-button size="small" type="primary" @click="handleCreate">新建文章</n-button>
|
||||||
</div>
|
</div>
|
||||||
<n-data-table
|
<n-data-table
|
||||||
@@ -11,8 +11,7 @@
|
|||||||
:columns="columns"
|
:columns="columns"
|
||||||
:pagination="pagination"
|
:pagination="pagination"
|
||||||
:row-key="(row) => row.id"
|
:row-key="(row) => row.id"
|
||||||
@update:checked-row-keys="handleCheck"
|
@update:checked-row-keys="handleCheck" />
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -37,9 +36,3 @@ function handleCheck(rowKeys) {
|
|||||||
if (rowKeys.length) $message.info(`选中${rowKeys.join(' ')}`)
|
if (rowKeys.length) $message.info(`选中${rowKeys.join(' ')}`)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.action-btns {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -10,28 +10,31 @@
|
|||||||
<icon-custom-logo mr30 text-50 />
|
<icon-custom-logo mr30 text-50 />
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</h5>
|
</h5>
|
||||||
<div mt-35 w-full max-w-360>
|
<div mt-30 w-full max-w-360>
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="loginInfo.name"
|
v-model:value="loginInfo.name"
|
||||||
autofocus
|
autofocus
|
||||||
class="text-16 items-center h-50 pl-10"
|
class="text-16 items-center h-50 pl-10"
|
||||||
placeholder="请输入用户名"
|
placeholder="admin"
|
||||||
:maxlength="20"
|
:maxlength="20">
|
||||||
>
|
|
||||||
</n-input>
|
</n-input>
|
||||||
</div>
|
</div>
|
||||||
<div mt-35 w-full max-w-360>
|
<div mt-30 w-full max-w-360>
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="loginInfo.password"
|
v-model:value="loginInfo.password"
|
||||||
class="text-16 items-center h-50 pl-10"
|
class="text-16 items-center h-50 pl-10"
|
||||||
type="password"
|
type="password"
|
||||||
show-password-on="mousedown"
|
show-password-on="mousedown"
|
||||||
placeholder="密码"
|
placeholder="123456"
|
||||||
:maxlength="20"
|
:maxlength="20"
|
||||||
@keydown.enter="handleLogin"
|
@keydown.enter="handleLogin" />
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div mt-35 w-full max-w-360>
|
|
||||||
|
<div mt-20 w-full max-w-360>
|
||||||
|
<n-checkbox :checked="isRemember" label="记住我" :on-update:checked="(val) => (isRemember = val)" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div mt-20 w-full max-w-360>
|
||||||
<n-button w-full h-50 rounded-5 text-16 type="primary" @click="handleLogin">登录</n-button>
|
<n-button w-full h-50 rounded-5 text-16 type="primary" @click="handleLogin">登录</n-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,8 +44,9 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { login } from '@/api/auth'
|
import { login } from '@/api/auth'
|
||||||
import { createLocalStorage } from '@/utils/cache'
|
import { lStorage } from '@/utils/cache'
|
||||||
import { setToken } from '@/utils/token'
|
import { setToken } from '@/utils/token'
|
||||||
|
import { useStorage } from '@vueuse/core'
|
||||||
|
|
||||||
const title = import.meta.env.VITE_APP_TITLE
|
const title = import.meta.env.VITE_APP_TITLE
|
||||||
|
|
||||||
@@ -50,17 +54,21 @@ const router = useRouter()
|
|||||||
const query = unref(router.currentRoute).query
|
const query = unref(router.currentRoute).query
|
||||||
|
|
||||||
const loginInfo = ref({
|
const loginInfo = ref({
|
||||||
name: 'admin',
|
name: '',
|
||||||
password: '123456',
|
password: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
const ls = createLocalStorage({ prefixKey: 'login_' })
|
initLoginInfo()
|
||||||
const lsLoginInfo = ls.get('loginInfo')
|
|
||||||
if (lsLoginInfo) {
|
function initLoginInfo() {
|
||||||
loginInfo.value.name = lsLoginInfo.name || ''
|
const localLoginInfo = lStorage.get('loginInfo')
|
||||||
loginInfo.value.password = lsLoginInfo.password || ''
|
if (localLoginInfo) {
|
||||||
|
loginInfo.value.name = localLoginInfo.name || ''
|
||||||
|
loginInfo.value.password = localLoginInfo.password || ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isRemember = useStorage('isRemember', false)
|
||||||
async function handleLogin() {
|
async function handleLogin() {
|
||||||
const { name, password } = loginInfo.value
|
const { name, password } = loginInfo.value
|
||||||
if (!name || !password) {
|
if (!name || !password) {
|
||||||
@@ -72,9 +80,12 @@ async function handleLogin() {
|
|||||||
const res = await login({ name, password: password.toString() })
|
const res = await login({ name, password: password.toString() })
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
$message.success('登录成功')
|
$message.success('登录成功')
|
||||||
ls.set('loginInfo', { name, password })
|
|
||||||
setToken(res.data.token)
|
setToken(res.data.token)
|
||||||
|
if (isRemember.value) {
|
||||||
|
lStorage.set('loginInfo', { name, password })
|
||||||
|
} else {
|
||||||
|
lStorage.remove('loginInfo')
|
||||||
|
}
|
||||||
if (query.redirect) {
|
if (query.redirect) {
|
||||||
const path = query.redirect
|
const path = query.redirect
|
||||||
Reflect.deleteProperty(query, 'redirect')
|
Reflect.deleteProperty(query, 'redirect')
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineConfig, presetAttributify, presetIcons, presetUno } from 'unocss'
|
import { defineConfig, presetAttributify, presetUno } from 'unocss'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
shortcuts: [
|
shortcuts: [
|
||||||
|
|||||||
Reference in New Issue
Block a user