first commit

main
expressgy 5 months ago
commit 744026ea26
  1. 1
      .env
  2. 14
      .eslintrc.cjs
  3. 30
      .gitignore
  4. 8
      .prettierrc.json
  5. 7
      .vscode/extensions.json
  6. 35
      README.md
  7. BIN
      docs/学习vue3/vue3.assets/3b3bd03e11464112b94ed3afd66e7fbf.png
  8. BIN
      docs/学习vue3/vue3.assets/48618779e6cc4d9c82bacbc11a59dcae.png
  9. BIN
      docs/学习vue3/vue3.assets/8761f9becf244a3e93973c3a03858dc6.png
  10. BIN
      docs/学习vue3/vue3.assets/c0eed55cafc0418c813d4a6e77ed2835.png
  11. BIN
      docs/学习vue3/vue3.assets/d791b0e7431b492b9d5dafe186d884b0.png
  12. BIN
      docs/学习vue3/vue3.assets/ecdb8a0d231140ab8584b83992c41b62.png
  13. 910
      docs/学习vue3/vue3.md
  14. 5
      docs/学习vue3/vue3内置组件.md
  15. BIN
      docs/学习vue3/vue3深入组件.assets/3d9f574cc12244ecb8dcab9ef49d7697.png
  16. 555
      docs/学习vue3/vue3深入组件.md
  17. 0
      docs/学习vue3/vue3路由.md
  18. 244
      docs/学习vue3/vue3逻辑复用.md
  19. 63
      docs/学习vue3/watchEffect实现原理.js
  20. 13
      index.html
  21. 8
      jsconfig.json
  22. 32
      package.json
  23. 2633
      pnpm-lock.yaml
  24. BIN
      public/favicon.ico
  25. 9
      src/App.vue
  26. 86
      src/assets/base.css
  27. 1
      src/assets/logo.svg
  28. 11
      src/assets/main.css
  29. 31
      src/components/CachingAndTransition/index.vue
  30. 18
      src/components/ColorComponent/ColorComponent.vue
  31. 44
      src/components/HelloWorld.vue
  32. 120
      src/components/MainFramework/HeaderBar/index.vue
  33. 30
      src/components/MainFramework/SideBar/MenuItem.vue
  34. 79
      src/components/MainFramework/SideBar/index.vue
  35. 84
      src/components/MainFramework/index.vue
  36. 7
      src/components/NotFound/index.vue
  37. 23
      src/components/TestComponent/index.vue
  38. 91
      src/components/TheWelcome.vue
  39. 86
      src/components/WelcomeItem.vue
  40. 7
      src/components/icons/IconCommunity.vue
  41. 7
      src/components/icons/IconDocumentation.vue
  42. 7
      src/components/icons/IconEcosystem.vue
  43. 7
      src/components/icons/IconSupport.vue
  44. 19
      src/components/icons/IconTooling.vue
  45. 16
      src/luojifuyong/fetch.js
  46. 23
      src/luojifuyong/mouse.js
  47. 43
      src/main.js
  48. 42
      src/router/auth/index.js
  49. 10
      src/router/index.js
  50. 29
      src/router/routeList/index.js
  51. 28
      src/router/routeList/main/index.js
  52. 12
      src/stores/counter.js
  53. 28
      src/stores/systemStore.js
  54. 15
      src/views/AboutView.vue
  55. 9
      src/views/HomeView.vue
  56. 20
      vite.config.js

@ -0,0 +1 @@
VITE_BASE_URL = "/GY"

@ -0,0 +1,14 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-prettier/skip-formatting'
],
parserOptions: {
ecmaVersion: 'latest'
}
}

30
.gitignore vendored

@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 4,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
}

@ -0,0 +1,7 @@
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}

@ -0,0 +1,35 @@
# vue3-test
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Compile and Minify for Production
```sh
npm run build
```
### Lint with [ESLint](https://eslint.org/)
```sh
npm run lint
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

@ -0,0 +1,910 @@
# 基础
```bash
npm create vue@latest
```
## 创建应用
### 应用配置
> **确保在挂载应用实例之前完成所有应用配置!**
- 应用级的错误处理器,用来捕获所有子组件上的错误
```javascript
app.config.errorHandler = (err) => {
/* 处理错误 */
}
```
- 注册全局资源,**注册一个组件**
```javascript
app.component('TodoDeleteButton', TodoDeleteButton)
```
### 多实例
```javascript
const app1 = createApp({
/* ... */
})
app1.mount('#container-1')
const app2 = createApp({
/* ... */
})
app2.mount('#container-2')
```
---
## 模板语法
> 如果偏好使用JavaScript,JSX 支持直接**手写渲染函数**而不采用模板。但JSX不会享受到和模板同等级别的**编译时优化**。
### 文本插值
```vue
<span>Message: {{ msg }}</span>
```
### 使用原始HTML,不被vue编译
```vue
<p>Using text interpolation: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
```
![原始HTML](./vue3.assets/c0eed55cafc0418c813d4a6e77ed2835.png)
![安全警告](./vue3.assets/3b3bd03e11464112b94ed3afd66e7fbf.png)
### Attribute 绑定
```vue
<div v-bind:id="dynamicId"></div>
<!--简写-->
<div :id="dynamicId"></div>
<!--同名简写 版本>=3.4-->
<!-- 与 :id="id" 相同 -->
<div :id></div>
<!-- 这也同样有效 -->
<div v-bind:id></div>
```
### _布尔型 Attribute ???_
当 isButtonDisabled 为真值或一个空字符串 (即 `<button disabled="">`) 时,元素会包含这个 disabled attribute。而当其为其他假值时 attribute 将被忽略。
### 动态绑定多个值
```vue
const objectOfAttrs = {
id: 'container',
class: 'wrapper'
}
<div v-bind="objectOfAttrs"></div>
```
### 使用 JavaScript 表达式
```vue
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div :id="`list-${id}`"></div>
```
### 调用函数
```vue
<time :title="toTitleDate(date)" :datetime="date">
{{ formatDate(date) }}
</time>
```
### 受限的全局访问
- Math 和 Date可以在标签中使用,其他的需要在配置中注册,`app.config.globalProperties`
```js
app.config.globalProperties.msg = 'hello'
export default {
mounted() {
console.log(this.msg) // 'hello'
}
}
```
### 动态参数
```vue
<!--
注意,参数表达式有一些约束,
参见下面“动态参数值的限制”与“动态参数语法的限制”章节的解释
-->
<a v-bind:[attributeName]="url"> ... </a>
<!-- 简写 -->
<a :[attributeName]="url"> ... </a>
```
### 动态事件
```vue
<a v-on:[eventName]="doSomething"> ... </a>
<!-- 简写 -->
<a @[eventName]="doSomething"> ... </a>
```
- 不要这样写
- 动态参数表达式因为某些字符的缘故有一些语法限制,比如空格和引号,在 HTML attribute 名称中都是不合法的。
```vue
<!-- 这会触发一个编译器警告 -->
<template>
<!-- <a :['foo' + bar]="value"> ... </a>-->
</template>
```
### _修饰符 Modifiers ?_
---
## 响应式基础
### 声明响应式状态
### ref的实际样子
```js
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
```
### 使用ref
```js
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() {
// 在 JavaScript 中需要 .value
count.value++
}
// 不要忘记同时暴露 increment 函数
return {
count,
increment
}
}
}
```
```vue
<div>{{ count }}</div>
<button @click="count++">
{{ count }}
</button>
```
### `<script setup>`
```vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }}
</button>
</template>
```
### DOM 更新时机
```js
import { nextTick } from 'vue'
async function increment() {
count.value++
await nextTick()
// 现在 DOM 已经更新了
}
```
### reactive()
- `reactive()` 将使对象本身具有响应性
```vue
<script setup>
import { reactive } from 'vue'
const state = reactive({ count: 0 })
</script>
<template>
<button @click="state.count++">
{{ state.count }}
</button>
</template>
```
- `reactive()` 返回的是一个原始对象的 Proxy
```js
const raw = {}
const proxy = reactive(raw)
// 代理对象和原始对象不是全等的
console.log(proxy === raw) // false
// 在同一个对象上调用 reactive() 会返回相同的代理
console.log(reactive(raw) === proxy) // true
// 在一个代理上调用 reactive() 会返回它自己
console.log(reactive(proxy) === proxy) // true
```
- **这个规则对嵌套对象也适用。依靠深层响应性,响应式对象内的嵌套对象依然是代理:**
```js
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false
```
### `reactive()` 的局限性
- 有限的值类型:不能持有如 string、number 或 boolean 这样的原始类型。
- 不能替换整个对象:
```js
let state = reactive({ count: 0 })
// 上面的 ({ count: 0 }) 引用将不再被追踪
// (响应性连接已丢失!)
state = reactive({ count: 1 })
```
- 对解构操作不友好
```js
const state = reactive({ count: 0 })
// 当解构时,count 已经与 state.count 断开连接
let { count } = state
// 不会影响原始的 state
count++
// 该函数接收到的是一个普通的数字
// 并且无法追踪 state.count 的变化
// 我们必须传入整个对象以保持响应性
callSomeFunction(state.count)
```
## 计算属性
### 基础示例
```vue
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
<template>
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
</template>
```
- 计算属性有缓存,当响应值没变化时,计算属性不会执行,如果用函数的话,函数每次都会执行
```js
// now永远不会更新,因为他没有用到响应值
const now = computed(() => Date.now())
```
### 可写计算属性
```vue
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
// getter
get() {
return firstName.value + ' ' + lastName.value
},
// setter
set(newValue) {
// 注意:我们这里使用的是解构赋值语法
[firstName.value, lastName.value] = newValue.split(' ')
}
})
</script>
```
## 类与样式绑定
```vue
<script setup>
const isActive = ref(true)
const hasError = ref(false)
</script>
<template>
<div
class="static"
:class="{ active: isActive, 'text-danger': hasError }"
></div>
</template>
```
渲染结果:
`<div class="static active"></div>`
### 组件的类会叠加
```vue
<!-- 子组件模板 -->
<p class="foo bar">Hi!</p>
<!-- 在使用组件时 -->
<MyComponent class="baz boo" />
<!--渲染出的 HTML 为:-->
<p class="foo bar baz boo">Hi!</p>
```
## 条件渲染基本没变
```vue
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
```
## 列表渲染
### `v-for`可以遍历对象,顺序基于Object.keys()
### 使用范围值 【整数值】
```vue
<!--n从1开始-->
<span v-for="n in 10">{{ n }}</span>
```
### `<template>` 上也可以用 v-for
## 事件处理
- `v-on` 指令 (简写为 `@`)
### 内联事件处理器
```vue
<script setup>
const count = ref(0)
</script>
<template>
<button @click="count++">Add 1</button>
<p>Count is: {{ count }}</p>
</template>
```
### 方法事件处理器
```vue
<script setup>
const name = ref('Vue.js')
function greet(event) {
alert(`Hello ${name.value}!`)
// `event` 是 DOM 原生事件
if (event) {
alert(event.target.tagName)
}
}
</script>
<template>
<!-- `greet` 是上面定义过的方法名 -->
<button @click="greet">Greet</button>
</template>
```
### 在内联事件处理器中访问事件参数
- `$event`
```vue
<script setup>
function warn(message, event) {
// 这里可以访问原生事件
if (event) {
event.preventDefault()
}
alert(message)
}
</script>
<template>
<!-- 使用特殊的 $event 变量 -->
<button @click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
<!-- 使用内联箭头函数 -->
<button @click="(event) => warn('Form cannot be submitted yet.', event)">
Submit
</button>
</template>
```
### 事件修饰符
- .stop
- .prevent
- .self
- .capture
- .once
- .passive
```vue
<!-- 单击事件将停止传递 -->
<a @click.stop="doThis"></a>
<!-- 提交事件将不再重新加载页面 -->
<form @submit.prevent="onSubmit"></form>
<!-- 修饰语可以使用链式书写 -->
<a @click.stop.prevent="doThat"></a>
<!-- 也可以只有修饰符 -->
<form @submit.prevent></form>
<!-- 仅当 event.target 是元素本身时才会触发事件处理器 -->
<!-- 例如:事件处理器不来自子元素 -->
<div @click.self="doThat">...</div>
```
![](./vue3.assets/8761f9becf244a3e93973c3a03858dc6.png)
`.capture`、`.once` 和 `.passive` 修饰符与原生 addEventListener 事件相对应:
```vue
<!-- 添加事件监听器时,使用 `capture` 捕获模式 -->
<!-- 例如:指向内部元素的事件,在被内部元素处理前,先被外部处理 -->
<div @click.capture="doThis">...</div>
<!-- 点击事件最多被触发一次 -->
<a @click.once="doThis"></a>
<!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 -->
<!-- 以防其中包含 `event.preventDefault()` -->
<div @scroll.passive="onScroll">...</div>
```
`.passive` 修饰符一般用于触摸事件的监听器,可以用来改善移动端设备的滚屏性能。
![](./vue3.assets/48618779e6cc4d9c82bacbc11a59dcae.png)
### 按键修饰符
- **$event.key**
- 按键别名
- .enter
- .tab
- .delete (捕获“Delete”和“Backspace”两个按键)
- .esc
- .space
- .up
- .down
- .left
- .right
- 系统按键修饰符
- .ctrl
- .alt
- .shift
- .meta
```vue
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />
<!-- Ctrl + 点击 -->
<div @click.ctrl="doSomething">Do something</div>
```
### `.exact` 修饰符允许精确控制触发事件所需的系统修饰符的组合。
```vue
<!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 仅当按下 Ctrl 且未按任何其他键时才会触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 仅当没有按下任何系统按键时触发 -->
<button @click.exact="onClick">A</button>
```
### 鼠标按键修饰符
- .left
- .right
- .middle
## 生命周期
```vue
<script setup>
import { onMounted } from 'vue'
onMounted(() => {
console.log(`the component is now mounted.`)
})
</script>
```
![生命周期](./vue3.assets/ecdb8a0d231140ab8584b83992c41b62.png)
## 侦听器
```vue
<script setup>
import { ref, watch } from 'vue'
const x = ref(0)
const y = ref(0)
// 单个 ref
watch(x, (newX, oldX) => {
console.log(`x is ${newX}`)
})
// getter 函数
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
</script>
```
- 不能直接侦听响应式对象的属性值
```js
const obj = reactive({ count: 0 })
// 错误,因为 watch() 得到的参数是一个 number
watch(obj.count, (count) => {
console.log(`count is: ${count}`)
})
// 提供一个 getter 函数
watch(
() => obj.count,
(count) => {
console.log(`count is: ${count}`)
}
)
```
### 深层侦听器 `{ deep: true }`
```js
const obj = reactive({ count: 0 })
watch(obj, (newValue, oldValue) => {
// 在嵌套的属性变更时触发
// 注意:`newValue` 此处和 `oldValue` 是相等的
// 因为它们是同一个对象!
})
watch(
() => state.someObject,
(newValue, oldValue) => {
// 注意:`newValue` 此处和 `oldValue` 是相等的
// *除非* state.someObject 被整个替换了
},
{ deep: true }
)
obj.count++
```
![](./vue3.assets/d791b0e7431b492b9d5dafe186d884b0.png)
### 即时回调的侦听器,**在创建侦听器时,立即执行一遍回调** `{ immediate: true }`
```js
watch(
source,
(newValue, oldValue) => {
// 立即执行,且当 `source` 改变时再次执行
},
{ immediate: true }
)
```
### 一次性侦听器 (版本>=3.4)
```js
watch(
source,
(newValue, oldValue) => {
// 当 `source` 变化时,仅触发一次
},
{ once: true }
)
```
### `watchEffect()`, 如果函数体内有响应式参数被调用,则会自动执行,且会立即执行一次
```js
watchEffect(async () => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${todoId.value}`
)
data.value = await response.json()
})
```
### _回调的触发时机 ?_
### 停止侦听器
```js
const unwatch = watchEffect(() => {})
// ...当该侦听器不再需要时
unwatch()
```
## 模板引用
```vue
<script setup>
import { ref, onMounted } from 'vue'
// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const input = ref(null)
onMounted(() => {
input.value.focus()
})
</script>
<template>
<input ref="input" />
</template>
```
### `v-for` 中的模板引用
- 应该注意的是,ref 数组并不保证与源数组相同的顺序。
```vue
<script setup>
import { ref, onMounted } from 'vue'
const list = ref([
/* ... */
])
const itemRefs = ref([])
onMounted(() => console.log(itemRefs.value))
</script>
<template>
<ul>
<li v-for="item in list" ref="itemRefs">
{{ item }}
</li>
</ul>
</template>
```
### 函数模板引用
```vue
<input :ref="(el) => { /* 将 el 赋值给一个数据属性或 ref 变量 */ }">
```
### 组件上的 ref
- `setup`组合式组件,需要手动暴露子组件的变量
```vue
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
// 像 defineExpose 这样的编译器宏不需要导入
defineExpose({
a,
b
})
</script>
```
## 组件基础
### 单文件组件 (简称 SFC) `.vue`
```vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">You clicked me {{ count }} times.</button>
</template>
```
### `.js`
```js
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return { count }
},
template: `
<button @click="count++">
You clicked me {{ count }} times.
</button>`
// 也可以针对一个 DOM 内联模板:
// template: '#my-template-element'
}
```
### 传递 props
```vue
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
</script>
<template>
<h4>{{ title }}</h4>
</template>
```
- 如果你没有使用 <script setup>props props props setup()
```js
export default {
props: ['title'],
setup(props) {
console.log(props.title)
}
}
```
### 自定义监听事件
```vue
<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
// 在setp中执行
// SFC组合式
const emit = defineEmits(['enlarge-text'])
emit('enlarge-text')
// 选项式
export default {
emits: ['enlarge-text'],
setup(props, ctx) {
ctx.emit('enlarge-text')
}
}
</script>
<template>
<button @click="$emit('enlarge-text')">Enlarge text</button>
</template>
```
```vue
<!--父组件-->
<script setup>
function oneMethod(event){
}
</script>
<template>
<BlogPost @enlarge-text="oneMethod"/>
</template>
```
### 插槽
```vue
<template>
<div class="alert-box">
<strong>This is an Error for Demo Purposes</strong>
<slot />
</div>
</template>
<style scoped>
.alert-box {
/* ... */
}
</style>
```
### 动态组件
```vue
<!-- currentTab 改变时组件也改变 -->
<component :is="tabs[currentTab]"></component>
```

@ -0,0 +1,5 @@
# 内置组件
## Transition
- 制作基于状态变化的过渡和动画

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

@ -0,0 +1,555 @@
# 深入组件
## 组件注册
### 全局注册
```js
import { createApp } from 'vue'
const app = createApp({})
app.component(
// 注册的名字
'MyComponent',
// 组件的实现
{
/* ... */
}
)
// 单文件组件
import MyComponent from './App.vue'
app.component('MyComponent', MyComponent)
// .component() 方法可以被链式调用
app
.component('ComponentA', ComponentA)
.component('ComponentB', ComponentB)
.component('ComponentC', ComponentC)
```
### 命名
- PascalCase:大驼峰
## Props
### Props 声明
- 使用 `<script setup>`
```vue
<script setup>
const props = defineProps(['foo'])
console.log(props.foo)
</script>
```
- 没有使用 `<script setup>`
```js
export default {
props: ['foo'],
setup(props) {
// setup() 接收 props 作为第一个参数
console.log(props.foo)
}
}
```
### 使用一个对象绑定多个 prop
```vue
<script setup>
const post = {
id: 1,
title: 'My Journey with Vue'
}
</script>
<template>
<BlogPost v-bind="post" />
<!--等价于:-->
<BlogPost :id="post.id" :title="post.title" />
</template>
```
### 单向数据流
- 不能重新赋值props
- 能修改引用类型,但是会造成损耗不推荐
### Prop 校验
```js
defineProps({
// 基础类型检查
// (给出 `null``undefined` 值则会跳过任何类型检查)
propA: Number,
// 多种可能的类型
propB: [String, Number],
// 必传,且为 String 类型
propC: {
type: String,
required: true
},
// Number 类型的默认值
propD: {
type: Number,
default: 100
},
// 对象类型的默认值
propE: {
type: Object,
// 对象或数组的默认值
// 必须从一个工厂函数返回。
// 该函数接收组件所接收到的原始 prop 作为参数。
default(rawProps) {
return { message: 'hello' }
}
},
// 自定义类型校验函数
// 在 3.4+ 中完整的 props 作为第二个参数传入
propF: {
validator(value, props) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
}
},
// 函数类型的默认值
propG: {
type: Function,
// 不像对象或数组的默认,这不是一个
// 工厂函数。这会是一个用来作为默认值的函数
default() {
return 'Default function'
}
}
})
```
## 组件事件
```vue
<!-- MyComponent -->
<button @click="$emit('someEvent')">click me</button>
<!--推荐使用 kebab-case 形式来编写监听器-->
<MyComponent @some-event="callback" />
<MyComponent @some-event.once="callback" />
```
![](./vue3深入组件.assets/3d9f574cc12244ecb8dcab9ef49d7697.png)
### 事件参数
```vue
<button @click="$emit('increaseBy', 1, 2, 3)">
Increase by 1
</button>
<MyButton @increase-by="(n, l, m) => count += n" />
```
### 声明触发的事件
- 我们在 `<template>` 中使用的 `$emit` 方法不能在组件的 `<script setup>` 部分中使用,但 `defineEmits()` 会返回一个相同作用的函数供我们使用:
- `defineEmits()` 宏不能在子函数中使用。如上所示,它必须直接放置在 `<script setup>` 的顶级作用域下。
```vue
<script setup>
// 这个必须写在最外曾
const emit = defineEmits(['inFocus', 'submit'])
function buttonClick() {
emit('submit')
}
export default {
emits: ['inFocus', 'submit'],
setup(props, ctx) {
ctx.emit('submit')
}
}
</script>
```
### 事件校验
- 要为事件添加校验,那么事件可以被赋值为一个函数,接受的参数就是抛出事件时传入 emit 的内容,返回一个布尔值来表明事件是否合法。
```vue
<script setup>
const emit = defineEmits({
// 没有校验
click: null,
// 校验 submit 事件
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
})
function submitForm(email, password) {
emit('submit', { email, password })
}
</script>
```
## 组件 v-model
- `v-model` 可以在组件上使用以实现双向绑定。
```vue
<script setup>
// 子组件
const model = defineModel()
// 使 v-model 必填
const model = defineModel({ required: true })
// 提供一个默认值
const model = defineModel({ default: 0 })
function update() {
// 以ref的方式调用
model.value++
}
</script>
<template>
<!-- 父组件Parent.vue -->
<Child v-model="count" />
</template>
```
### v-model 的参数
```vue
<MyComponent v-model:title="bookTitle" />
```
```vue
<!-- MyComponent.vue -->
<script setup>
const title = defineModel('title')
const title = defineModel('title', { required: true })
</script>
<template>
<input type="text" v-model="title" />
</template>
```
### 多个 v-model 绑定
```vue
<UserName
v-model:first-name="first"
v-model:last-name="last"
/>
```
```vue
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>
<template>
<input type="text" v-model="firstName" />
<input type="text" v-model="lastName" />
</template>
```
# ? 传递对象是否还在使用ref响应式的参数
## 透传 Attributes
### Attributes 继承
```vue
<!--父组件-->
<MyButton class="large" />
<!-- <MyButton> 子组件的模板 -->
<button>click me</button>
<!--最后渲染出的 DOM 结果-->
<button class="large">click me</button>
```
### `class``style`合并
- `template`下只有一个元素的话会继承,多个的话就不会,见**多根节点的 Attributes 继承**
### `v-on` 监听器继承
### 深层组件继承——重写组件的话,会被子组件调用的子组件继承
### 禁用 Attributes 继承
```vue
<script setup>
defineOptions({
inheritAttrs: false
})
// ...setup 逻辑
</script>
```
### 多根节点的 Attributes 继承
- 和单根节点组件有所不同,有着多个根节点的组件没有自动 attribute 透传行为。如果 $attrs 没有被显式绑定,将会抛出一个运行时警告。
- 如果 <CustomLayout> 有下面这样的多根节点模板,由于 Vue 不知道要将 attribute 透传到哪里,所以会抛出一个警告。
- 如果 $attrs 被显式绑定,则不会有警告:
```vue
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
```
### 在 JavaScript 中访问透传 Attributes
```vue
<script setup>
import { useAttrs } from 'vue'
const attrs = useAttrs()
// 如果没有使用 <script setup>attrs setup()
export default {
setup(props, ctx) {
// 透传 attribute 被暴露为 ctx.attrs
console.log(ctx.attrs)
}
}
</script>
```
## 插槽 Slots
### 默认内容
```vue
<button type="submit">
<slot>
Submit <!-- 默认内容 -->
</slot>
</button>
```
### 具名插槽
- `v-slot` 有对应的简写 `#`,因此 `<template v-slot:header>` 可以简写为 `<template #header>`。其意思就是“将这部分模板片段传入子组件的 `header` 插槽中”。
```vue
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
<!--父组件-->
<BaseLayout>
<template v-slot:header>
<!-- header 插槽的内容放这里 -->
</template>
</BaseLayout>
<BaseLayout>
<template #header>
<h1>Here might be a page title</h1>
</template>
<template #default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template #footer>
<p>Here's some contact info</p>
</template>
</BaseLayout>
```
### 动态插槽名
```vue
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
<!-- 缩写为 -->
<template #[dynamicSlotName]>
...
</template>
</base-layout>
```
### 作用域插槽——使用自组建的传值
```vue
<!-- <MyComponent> 的模板 -->
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
<MyComponent v-slot="{ text, count }">
{{ text }} {{ count }}
</MyComponent>
```
### 具名作用域插槽
```vue
<MyComponent>
<template #header="headerProps">
{{ headerProps }}
</template>
<template #default="defaultProps">
{{ defaultProps }}
</template>
<template #footer="footerProps">
{{ footerProps }}
</template>
</MyComponent>
<!--向具名插槽中传入 props:-->
<slot name="header" message="hello"></slot>
```
## 依赖注入
- 解决多级props传递
### Provide (提供)
```vue
<script setup>
import { provide } from 'vue'
provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
</script>
```
### 应用层 Provide
```js
import { createApp } from 'vue'
const app = createApp({})
app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
```
### Inject (注入)
```vue
<script setup>
import { inject } from 'vue'
const message = inject('message')
// 如果没有祖先组件提供 "message"
// `value` 会是 "这是默认值"
const value = inject('message', '这是默认值')
// 在一些场景中,默认值可能需要通过调用一个函数或初始化一个类来取得。为了避免在用不到默认值的情况下进行不必要的计算或产生副作用,我们可以使用工厂函数来创建默认值:
const value = inject('key', () => new ExpensiveClass(), true)
// 第三个参数表示默认值应该被当作一个工厂函数
</script>
```
### 使用 Symbol 作注入名
- 包含非常多的依赖提供,很麻烦
```js
// keys.js
export const myInjectionKey = Symbol()
```
## 异步组件 ? 懒加载?
### 基本用法
```js
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
// ...从服务器获取组件
resolve(/* 获取到的组件 */)
})
})
// ... 像使用其他一般组件一样使用 `AsyncComp`
```
### ES 模块动态导入也会返回一个 Promise
```js
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
app.component('MyComponent', defineAsyncComponent(() =>
import('./components/MyComponent.vue')
))
```
```vue
<script setup>
import { defineAsyncComponent } from 'vue'
const AdminPage = defineAsyncComponent(() =>
import('./components/AdminPageComponent.vue')
)
</script>
<template>
<AdminPage />
</template>
```
### 加载与错误状态
```js
const AsyncComp = defineAsyncComponent({
// 加载函数
loader: () => import('./Foo.vue'),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent,
// 展示加载组件前的延迟时间,默认为 200ms
delay: 200,
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超时了
// 也会显示这里配置的报错组件,默认值是:Infinity
timeout: 3000
})
```

@ -0,0 +1,244 @@
# 逻辑复用
> 高深莫测
## 什么是“组合式函数”?
- 利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数
```js
// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'
// 按照惯例,组合式函数名以“use”开头
export function useMouse() {
// 被组合式函数封装和管理的状态
const x = ref(0)
const y = ref(0)
// 组合式函数可以随时更改其状态。
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
// 一个组合式函数也可以挂靠在所属组件的生命周期上
// 来启动和卸载副作用
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
// 通过返回值暴露所管理的状态
return { x, y }
}
```
```vue
<script setup>
import { useMouse } from './mouse.js'
const { x, y } = useMouse()
// x,y是可以监听变化的
</script>
<template>Mouse position is at: {{ x }}, {{ y }}</template>
```
### 异步状态示例
- 封装请求
```js
// fetch.js
import { ref } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
fetch(url)
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err))
return { data, error }
}
```
```vue
<script setup>
import { useFetch } from './fetch.js'
const { data, error } = useFetch('...')
</script>
```
### 接收响应式状态
```js
// fetch.js
import { ref, toValue, watchEffect } from 'vue'
// url必须是响应式参数才能被watchEffect监听到
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
setTimeout(() => {
data.value = "数据" + url.value
}, 3000)
watchEffect(() => {
data.value = "数据SSS" + toValue(url)
})
return { data, error }
}
```
### 约定和最佳实践
### 命名: 用驼峰命名法命名,以“use”作为开头。
### 输入参数
- 最好处理一下输入参数是 ref 或 getter 而非原始值的情况。可以利用 toValue() 工具函数来实现
```js
import { toValue } from 'vue'
function useFeature(maybeRefOrGetter) {
// 如果 maybeRefOrGetter 是一个 ref 或 getter,
// 将返回它的规范化值。
// 否则原样返回。
const value = toValue(maybeRefOrGetter)
}
```
### 返回值
- 使用 `ref()` 而不是 `reactive()`, 使用一个不是响应式的对象包含响应式参数,可以被很好的解构赋值
## 自定义指令
- 由一个包含类似组件生命周期钩子的对象来定义
```vue
<script setup>
// 在模板中启用 v-focus
const vFocus = {
mounted: (el) => el.focus()
}
// 选项式
export default {
setup() {
/*...*/
},
directives: {
// 在模板中启用 v-focus
focus: {
/* ... */
}
}
}
</script>
<template>
<!-- 使用后,会在挂在这个元素时聚焦-->
<input v-focus />
</template>
```
### 指令钩子
```js
const myDirective = {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created(el, binding, vnode, prevVnode) {
// 下面会介绍各个参数的细节
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode, prevVnode) {}
}
```
### 全局
```js
const app = createApp({})
// 使 v-focus 在所有组件中都可用
app.directive('focus', {
/* ... */
})
```
### 钩子参数
- el:指令绑定到的元素。这可以用于直接操作 DOM。
- binding:一个对象,包含以下属性。
- value:传递给指令的值。例如在 v-my-directive="1 + 1" 中,值是 2。
- oldValue:之前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否更改,它都可用。
- arg:传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo 中,参数是 "foo"。
- modifiers:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }。
- instance:使用该指令的组件实例。
- dir:指令的定义对象。
-
- vnode:代表绑定元素的底层 VNode。
- prevVnode:代表之前的渲染中指令所绑定元素的 VNode。仅在 beforeUpdate 和 updated 钩子中可用。
### 在组件上使用
- 会透传,但是不建议在组件上使用
## 插件
### 使用
```js
import { createApp } from 'vue'
const app = createApp({})
app.use(myPlugin, {
/* 可选的选项 */
})
```
### 插件常见场景
1. 通过 app.component() 和 app.directive() 注册一到多个全局组件或自定义指令。
2. 通过 app.provide() 使一个资源可被注入进整个应用。
3. 向 app.config.globalProperties 中添加一些全局实例属性或方法
4. 一个可能上述三种都包含了的功能库 (例如 vue-router)。
### 编写插件
```js
// plugins/i18n.js
export default {
install: (app, options) => {
// 在这里编写插件代码
}
}
```

@ -0,0 +1,63 @@
function isObject(obj) {
return obj !== null && typeof obj === 'object';
}
function reactive(target) {
if (!isObject(target)) return target;
const handler = {
get(target, key, receiver) {
track(target, key);
const result = Reflect.get(target, key, receiver);
return isObject(result) ? reactive(result) : result;
},
set(target, key, value, receiver) {
const oldValue = target[key];
if (oldValue !== value) {
const result = Reflect.set(target, key, value, receiver);
trigger(target, key);
return result;
}
}
};
return new Proxy(target, handler);
}
const targetMap = new WeakMap();
const effectStack = [];
function track(target, key) {
if (effectStack.length) {
let depsMap = targetMap.get(target);
if (!depsMap) targetMap.set(target, (depsMap = new Map()));
let dep = depsMap.get(key);
if (!dep) depsMap.set(key, (dep = new Set()));
dep.add(effectStack[effectStack.length - 1]);
}
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (depsMap) {
const effects = new Set([...(depsMap.get(key) || [])]);
effects.forEach(effect => effect());
}
}
function watchEffect(effect) {
effectStack.push(effect);
try {
return effect();
} finally {
effectStack.pop();
}
}
// 使用示例
const data = reactive({ count: 0 });
watchEffect(() => {
console.log(`Count is: ${data.count}`);
});
setTimeout(() => {
data.count++; // 触发副作用,在控制台打印出新的count值
}, 1000);

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

@ -0,0 +1,32 @@
{
"name": "vue3-test",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
"format": "prettier --write src/"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"element-plus": "^2.7.1",
"pinia": "^2.1.7",
"vue": "^3.4.21",
"vue-router": "^4.3.0"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.8.0",
"@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/eslint-config-prettier": "^9.0.0",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.23.0",
"prettier": "^3.2.5",
"sass": "^1.75.0",
"vite": "^5.2.8",
"vite-plugin-vue-devtools": "^7.0.25"
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -0,0 +1,9 @@
<script setup>
import { RouterLink, RouterView } from 'vue-router'
</script>
<template>
<RouterView />
</template>
<style scoped></style>

@ -0,0 +1,86 @@
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
font-weight: normal;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition:
color 0.5s,
background-color 0.5s;
line-height: 1.6;
font-family:
Inter,
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
Roboto,
Oxygen,
Ubuntu,
Cantarell,
'Fira Sans',
'Droid Sans',
'Helvetica Neue',
sans-serif;
font-size: 14px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

After

Width:  |  Height:  |  Size: 276 B

@ -0,0 +1,11 @@
@import './base.css';
#app {
position: relative;
height: 100vh;
width: 100vw;
overflow: hidden;
box-sizing: border-box;
/*max-width: 1280px;*/
font-weight: normal;
}

@ -0,0 +1,31 @@
<script setup>
import { RouterView } from 'vue-router'
import { useSystemStore } from '@/stores/systemStore.js'
const systemStore = useSystemStore()
</script>
<template>
<RouterView v-slot="{ Component }">
<transition name="mainPage">
<keep-alive :include="systemStore.menuTagList.map(i => i.name).join(',')">
<component :is="Component" />
</keep-alive>
</transition>
</RouterView>
</template>
<style scoped lang="scss">
.mainPage-fade-enter-active {
transition: all 0.3s ease-out;
}
.mainPage-fade-leave-active {
transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}
.mainPage-fade-enter-from,
.mainPage-fade-leave-to {
transform: translateX(20px);
opacity: 0;
}
</style>

@ -0,0 +1,18 @@
<script setup>
import { ref } from 'vue'
const input = ref('')
</script>
<template>
<div class="aaa">
<el-input v-model="input" style="width: 240px" placeholder="Please input" />
</div>
</template>
<style scoped>
.aaa {
width: 100%;
height: 100%;
background: #181818;
}
</style>

@ -0,0 +1,44 @@
<script setup>
defineProps({
msg: {
type: String,
required: true
}
})
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
Youve successfully created a project with
<a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>.
</h3>
</div>
</template>
<style scoped>
h1 {
font-weight: 500;
font-size: 2.6rem;
position: relative;
top: -10px;
}
h3 {
font-size: 1.2rem;
}
.greetings h1,
.greetings h3 {
text-align: center;
}
@media (min-width: 1024px) {
.greetings h1,
.greetings h3 {
text-align: left;
}
}
</style>

@ -0,0 +1,120 @@
<script setup>
import { ArrowRight, Operation } from '@element-plus/icons-vue'
import { useSystemStore } from '@/stores/systemStore.js'
const systemStore = useSystemStore()
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
//
const handleCloseMenuTag = (item, index) => {
systemStore.menuTagList.splice(index, 1)
systemStore.menuTagList.slice(-1)[0].activation = true
router.push(systemStore.menuTagList.slice(-1)[0].path)
}
//
const handleChangeMenuTag = (item, index) => {
systemStore.menuTagList.forEach((itemL,indexL) => {
if(index == indexL){
itemL.activation = true
router.push(systemStore.menuTagList[indexL].path)
}else{
itemL.activation = false
}
})
}
</script>
<template>
<header class="mainFrameworkHeader">
<div class="lineUp">
<div class="menuAndPageContainer">
<div class="menuBut">
<el-button
type="primary"
@click="systemStore.handleChangeMenuExpandStatus"
:icon="Operation"
circle
/>
</div>
<div class="pageList">
<el-breadcrumb :separator-icon="ArrowRight">
<TransitionGroup name="menuBreadcrumb">
<el-breadcrumb-item v-for="item in route.matched" :key="item.name" class="menuBreadcrumbItem">{{item.meta.title}}</el-breadcrumb-item>
</TransitionGroup>
</el-breadcrumb>
</div>
</div>
<div class="userDataContainer">
<div>用户信息</div>
</div>
</div>
<div class="lineDown">
<el-tag
class="menuTag"
v-for="(item, index) in systemStore.menuTagList"
:key="item.name"
type="primary"
:effect="item.activation ? `dark` : `plain`"
round
:closable="!item.notClose"
@close="handleCloseMenuTag(item, index)"
@click="handleChangeMenuTag(item, index)"
>
{{ item.title }}
</el-tag>
</div>
</header>
</template>
<style scoped lang="scss">
.mainFrameworkHeader {
position: relative;
& > div.lineUp {
position: relative;
display: flex;
padding: 1rem;
border-bottom: 1px solid #cdcdcd;
& > div.menuAndPageContainer {
position: relative;
flex-shrink: 0;
display: flex;
width: 300px;
& > div.menuBut {
position: relative;
}
& > div.pageList {
position: relative;
padding-left: 20px;
display: flex;
align-items: center;
}
}
& > div.userDataContainer {
position: relative;
flex: 1;
display: flex;
flex-direction: row-reverse;
}
}
}
//
.menuTag{
cursor: pointer;
margin: 0 5px;
}
//
.menuBreadcrumb-enter-active{
transition: all 0.3s ease;
}
.menuBreadcrumb-leave-active {
position: absolute;
transition: all 0.3s ease;
white-space: nowrap;
}
.menuBreadcrumb-enter-from,
.menuBreadcrumb-leave-to {
opacity: 0;
transform: translateX(20px);
}
</style>

@ -0,0 +1,30 @@
<script setup>
import MenuItem from '@/components/MainFramework/SideBar/MenuItem.vue'
const { routeList, rootPath } = defineProps(['routeList', 'rootPath'])
</script>
<template>
<template v-for="(item, index) of routeList" :key="item.path">
<el-sub-menu
v-if="item?.children && item.children.length > 0"
:disabled="item?.meta?.disabled"
:index="`${rootPath}/${item.name}`"
>
<template #title>
<el-icon><HomeFilled /></el-icon>
<span>{{ item.meta.title }}</span>
</template>
<MenuItem :routeList="item.children" :rootPath="`${rootPath}/${item.name}`" />
</el-sub-menu>
<el-menu-item
v-else-if="item.name"
:disabled="item?.meta?.disabled"
:index="`${rootPath}/${item.name}`"
>
<el-icon><Management /></el-icon>
<span>{{ item.meta.title }}</span>
</el-menu-item>
</template>
</template>
<style scoped></style>

@ -0,0 +1,79 @@
<script setup>
import MenuItem from '@/components/MainFramework/SideBar/MenuItem.vue'
import { useSystemStore } from '@/stores/systemStore.js'
const systemStore = useSystemStore()
const handleOpen = (key, keyPath) => {
console.log(key, keyPath)
}
const handleClose = (key, keyPath) => {
console.log(key, keyPath)
}
</script>
<!--default-active 页面加载时默认激活菜单的 index string-->
<!--unique-opened 是否只保持一个子菜单的展开-->
<!--:collapse-transition="false" 展开动画-->
<template>
<aside class="mainSideMenuContainer">
<header class="mainSideMenuContainerHeader">
<div>icon</div>
<Transition name="mainSideMenuContainerHeader">
<div v-if="systemStore.menuExpandStatus">这里可以放系统名称</div>
</Transition>
</header>
<nav>
<el-menu
class="mainmenu"
:default-active="systemStore.nowRoutePath"
:collapse="!systemStore.menuExpandStatus"
@open="handleOpen"
@close="handleClose"
:router="true"
>
<MenuItem :routeList="systemStore.menuList" rootPath="" />
</el-menu>
</nav>
</aside>
</template>
<style scoped lang="scss">
.mainSideMenuContainer {
position: relative;
height: 100%;
display: flex;
flex-direction: column;
& > header.mainSideMenuContainerHeader {
position: relative;
flex-shrink: 0;
height: var(--mainFramework-header-height);
}
& > nav {
position: relative;
flex: 1;
overflow: hidden;
& > ul {
position: relative;
height: 100%;
overflow: auto;
max-width: 300px;
}
}
}
.mainmenu:not(.el-menu--collapse) {
width: 200px;
}
//
.mainSideMenuContainerHeader-enter-active,
.mainSideMenuContainerHeader-leave-active {
transition: opacity 0.5s ease;
}
.mainSideMenuContainerHeader-enter-from,
.mainSideMenuContainerHeader-leave-to {
opacity: 0;
}
</style>

@ -0,0 +1,84 @@
<script setup>
import HeaderBar from '@/components/MainFramework/HeaderBar/index.vue'
import SideBar from '@/components/MainFramework/SideBar/index.vue'
import { RouterView } from 'vue-router'
import CachingAndTransition from '@/components/CachingAndTransition/index.vue'
</script>
<template>
<div class="mainFramework">
<SideBar class="mainSide" />
<div class="mainBodyContainer">
<HeaderBar class="mainHeader" />
<div class="mainBody">
<CachingAndTransition/>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.mainFramework {
position: relative;
height: 100%;
display: flex;
// header
--mainFramework-header-height: 100px;
& > .mainSide {
position: relative;
flex-shrink: 0;
height: 100%;
overflow: hidden;
}
& > .mainBodyContainer {
position: relative;
flex: 1;
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
& .mainHeader {
position: relative;
flex-shrink: 0;
height: var(--mainFramework-header-height);
}
& > .mainBody {
position: relative;
flex: 1;
overflow: hidden;
margin: 10px;
border-radius: 5px;
overflow: hidden;
box-shadow: 2px 2px 10px 0px #aaaaaa66;
background: #fff;
}
& > div.mainBody.mainNoData {
position: relative;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
}
}
.mainPage-enter-active {
transition: all 0.3s ease-out;
z-index: 3;
}
.mainPage-leave-active {
position: absolute;
transition: all 0.3s cubic-bezier(1, 0.5, 0.8, 1);
}
.mainPage-enter-from,
.mainPage-leave-to {
transform: translateX(20px);
opacity: 0;
}
</style>

@ -0,0 +1,7 @@
<script setup></script>
<template>
<div>NOTFOUND 404</div>
</template>
<style scoped></style>

@ -0,0 +1,23 @@
<script setup>
import { ref, toValue, useAttrs, watchEffect } from 'vue'
const attrs = useAttrs()
const num = ref(0)
function test() {
console.log(toValue(num))
}
setTimeout(() => {
num.value++
}, 3000)
watchEffect(() => {
test()
console.log('??')
})
</script>
<template>
<button class="aaa">哈哈哈</button>
</template>
<style scoped></style>

@ -0,0 +1,91 @@
<script setup>
import WelcomeItem from './WelcomeItem.vue'
import DocumentationIcon from './icons/IconDocumentation.vue'
import ToolingIcon from './icons/IconTooling.vue'
import EcosystemIcon from './icons/IconEcosystem.vue'
import CommunityIcon from './icons/IconCommunity.vue'
import SupportIcon from './icons/IconSupport.vue'
</script>
<template>
<WelcomeItem>
<template #icon>
<DocumentationIcon />
</template>
<template #heading>Documentation</template>
Vues
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
provides you with all information you need to get started.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<ToolingIcon />
</template>
<template #heading>Tooling</template>
This project is served and bundled with
<a href="https://vitejs.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>.
The recommended IDE setup is
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a> +
<a href="https://github.com/johnsoncodehk/volar" target="_blank" rel="noopener">Volar</a>.
If you need to test your components and web pages, check out
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a> and
<a href="https://on.cypress.io/component" target="_blank" rel="noopener"
>Cypress Component Testing</a
>.
<br />
More instructions are available in <code>README.md</code>.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<EcosystemIcon />
</template>
<template #heading>Ecosystem</template>
Get official tools and libraries for your project:
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>,
and
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a
>. If you need more resources, we suggest paying
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener"
>Awesome Vue</a
>
a visit.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<CommunityIcon />
</template>
<template #heading>Community</template>
Got stuck? Ask your question on
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>, our official
Discord server, or
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
>StackOverflow</a
>. You should also subscribe to
<a href="https://news.vuejs.org" target="_blank" rel="noopener">our mailing list</a> and
follow the official
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
twitter account for latest news in the Vue world.
</WelcomeItem>
<WelcomeItem>
<template #icon>
<SupportIcon />
</template>
<template #heading>Support Vue</template>
As an independent project, Vue relies on community backing for its sustainability. You can
help us by
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
</WelcomeItem>
</template>

@ -0,0 +1,86 @@
<template>
<div class="item">
<i>
<slot name="icon"></slot>
</i>
<div class="details">
<h3>
<slot name="heading"></slot>
</h3>
<slot></slot>
</div>
</div>
</template>
<style scoped>
.item {
margin-top: 2rem;
display: flex;
position: relative;
}
.details {
flex: 1;
margin-left: 1rem;
}
i {
display: flex;
place-items: center;
place-content: center;
width: 32px;
height: 32px;
color: var(--color-text);
}
h3 {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 0.4rem;
color: var(--color-heading);
}
@media (min-width: 1024px) {
.item {
margin-top: 0;
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
}
i {
top: calc(50% - 25px);
left: -26px;
position: absolute;
border: 1px solid var(--color-border);
background: var(--color-background);
border-radius: 8px;
width: 50px;
height: 50px;
}
.item:before {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
bottom: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:after {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
top: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:first-of-type:before {
display: none;
}
.item:last-of-type:after {
display: none;
}
}
</style>

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
/>
</svg>
</template>

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
<path
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
/>
</svg>
</template>

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
<path
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
/>
</svg>
</template>

@ -0,0 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
<path
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
/>
</svg>
</template>

@ -0,0 +1,19 @@
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true"
role="img"
class="iconify iconify--mdi"
width="24"
height="24"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 24 24"
>
<path
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
fill="currentColor"
></path>
</svg>
</template>

@ -0,0 +1,16 @@
// fetch.js
import { ref, toValue, watchEffect } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
setTimeout(() => {
data.value = '数据' + url.value
}, 3000)
watchEffect(() => {
data.value = '数据SSS' + toValue(url)
})
return { data, error }
}

@ -0,0 +1,23 @@
// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'
// 按照惯例,组合式函数名以“use”开头
export function useMouse() {
// 被组合式函数封装和管理的状态
const x = ref(0)
const y = ref(0)
// 组合式函数可以随时更改其状态。
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
// 一个组合式函数也可以挂靠在所属组件的生命周期上
// 来启动和卸载副作用
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
// 通过返回值暴露所管理的状态
return { x, y }
}

@ -0,0 +1,43 @@
// 导入基础样式
import './assets/main.css'
// 导入Vue初始化程序
import { createApp } from 'vue'
// 导入Pinia状态管理
import { createPinia } from 'pinia'
// 引入根组件
import App from './App.vue'
// 引入路由表
import router from './router'
// 引入Element Plus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
// 路由守卫
import authRouter from '@/router/auth/index.js'
// 创建根组件渲染器
const app = createApp(App)
// 使用状态管理器
app.use(createPinia())
// 使用路由
app.use(router)
// 使用ElementPlus
app.use(ElementPlus)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
// 挂载渲染节点
app.mount('#app')
app.use(authRouter, {
router
})
app.component('CacheRouterView', () => import('@/components/CachingAndTransition/index.vue'))
// 路由守卫

@ -0,0 +1,42 @@
import { useSystemStore } from '@/stores/systemStore.js'
export default function authRouter(app, option){
const {router} = option;
const systemStore = useSystemStore()
router.beforeEach(async (to, from) => {
})
router.afterEach(async (to, from) => {
await menuTag(to, systemStore)
systemStore.nowRoutePath = to.path
})
}
// 标签页
async function menuTag(to, systemStore){
console.log(to)
const {name, meta, path } = to
if(name){
const menuTagList = systemStore.menuTagList;
let existTag = false
menuTagList.forEach(tag => {
if(tag.name == name){
existTag = true;
tag.activation = true
}else{
tag.activation = false
}
})
if(!existTag){
menuTagList.push({
name,
title: meta.title,
path,
activation: true,
})
}
}
}

@ -0,0 +1,10 @@
import { createRouter, createWebHistory } from 'vue-router'
import routeList from '@/router/routeList/index.js'
const router = createRouter({
history: createWebHistory(import.meta.env.VITE_BASE_URL),
routes: routeList
})
export default router

@ -0,0 +1,29 @@
import mainChildrem from './main/index.js'
export default [
{
path: '',
redirect: '/home'
},
{
path: '/home',
name: 'home',
component: () => import('@/components/MainFramework/index.vue'),
meta: {
title: '首页'
}
},
{
path: '/main',
name: 'main',
component: () => import('@/components/MainFramework/index.vue'),
children: mainChildrem,
meta: {
title: '设置'
}
},
// 其他路由...
{
path: '/:pathMatch(.*)*',
component: () => import('@/components/NotFound/index.vue')
}
]

@ -0,0 +1,28 @@
export default [
{
path: 'about',
name: 'about',
component: async () => {
const a = await import('@/components/ColorComponent/ColorComponent.vue');
a.default.__name = 'about'
return a
},
children: [],
meta: {
title: '关于'
}
},
{
path: 'myhome',
name: 'myhome',
component: async () => {
const a = await import('@/views/HomeView.vue')
a.default.__name = 'myhome'
return a
},
children: [],
meta: {
title: '我家'
}
}
]

@ -0,0 +1,12 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})

@ -0,0 +1,28 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
import routeList from '@/router/routeList/index.js'
export const useSystemStore = defineStore('system', () => {
// 菜单开合状态
const menuExpandStatus = ref(true);
// 菜单列表
const menuList = ref(routeList);
// 已打开的菜单
const menuTagList = ref([{
name: 'home',
notClose: true,
title: '首页',
path: '/home'
}])
// 当前路由菜单
const nowRoutePath = ref('home');
// 可缓存列表
const keepAliveList = []
// 开合菜单
function handleChangeMenuExpandStatus() {
menuExpandStatus.value = !menuExpandStatus.value
}
return { menuExpandStatus, handleChangeMenuExpandStatus, menuList, menuTagList, nowRoutePath, keepAliveList }
})

@ -0,0 +1,15 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>
<style>
@media (min-width: 1024px) {
.about {
min-height: 100vh;
display: flex;
align-items: center;
}
}
</style>

@ -0,0 +1,9 @@
<script setup>
import TheWelcome from '../components/TheWelcome.vue'
</script>
<template>
<main>
<TheWelcome />
</main>
</template>

@ -0,0 +1,20 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import VueDevTools from 'vite-plugin-vue-devtools'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
vueJsx(),
VueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
Loading…
Cancel
Save