Vue learning notes (II) todolist to do list cases based on vue2 | localstorage | local storage | vue2 publishing and subscription | vue2 supported animation classes | JavaScript prototype objects

You 2022-06-23 18:55:14 阅读数:89

vuelearningnotesiitodolist

One 、 Reference material


Two 、 Running environment


  • Windows11
  • Visual Studio Code v2022
  • Node.js v16.5.01
  • Vue/cli v5.0.6

3、 ... and 、 Case needs analysis


Based on the previous study , End use Vue-CLI The scaffold realizes TodoList The effect of the case is shown in the figure below :

 Insert picture description here
Here are some requirements for the case :

  1. Make sure the page is refreshed , The current to-do data will not be lost .
  2. Provide editing function , You can modify the added to-do items .
  3. To be familiar with Vue2 Simple use of animation styles , to TodoList Add a dynamic effect .

The final effect is as follows :

 Insert picture description here

Four 、 Related knowledge


4.1 JavaScript Prototype object

Reference material : Novice tutorial JavaScript Higher order

JavaScript Object constructors in

function Person(name, age) {

this.name = name;
this.age = age;
}
var me = new Person("uni",22);
var myGirl = new Person("who", 22);

stay JavaScript You cannot add a new attribute to an instance object that already has a constructor in

For example, add an attribute hobby Show interest

Error model :

Person.hobby = " having dinner ";

Correct demonstration : ( Modify the original object declaration )

function Person(name, age, hobby) {

this.name = name;
this.age = age;
this.hobby = hobby;
}

1. prototype Inherit

be-all JavaScript All objects will come from a prototype( Prototype object ) Inherit properties and methods in :

such as JS Some self-contained objects :

  • Date Objects from Date.prototype Inherit
  • Array Objects from Array.prototype Inherit

Empathy , We defined Person Objects will also start from Person.prototype Inherit properties and methods .

all JavaScript The objects in are all located at the top of the prototype chain Object Example , This is similar to Java All classes in the language are Object Subclasses of , There are Object Class toString() Method .

 Insert picture description here

JavaScript Object has a chain that points to a prototype object . When trying to access the properties of an object , It doesn't just search for the object , It also searches for the prototype of the object , And the prototype of the object , Search up one by one , Until you find a property that matches a name or you reach the end of the prototype chain ( namely Object).

In short , Whether it's JS Built in Date object , Array object , It's our custom Person Objects are from Object.prototype Inherit .

2. adopt prototype Add properties and methods

Applicable scenario :

  • Add new properties or methods to all existing objects
  • Add a property or method to the constructor of an object

The application case :

  • Add a new attribute to the constructor of the object
Person.prototype.hobby= " having dinner ";
  • Add a new method to the constructor of the object
function Person(first, last, age, eyecolor) {

this.name = name;
this.age = age;
}
Person.prototype.hobby = function() {

return this.name + " My hobby is eating ";
};

Specific cases :

So let's define one Person The constructor of the object , Only name and age Two properties , Create a based on the constructor uni object , When the button is clicked, the object will be prototype The prototype object adds a new property and a new method , And then through alert Print out :

The specific implementation code is as follows ( The file format is .html):

<!DOCTYPE html>
<head>
<h1> Test content </h1>
<button onclick="test(this)"> Initialize object </button>
</head>
<body>
<script type="text/javascript"> function Person(name, age){
 this.name = name this.age = age } let uni = new Person('uni', 22) function test(btn){
 // Get the upper level h1 label  let h1 = btn.previousElementSibling // adopt prototype Add attribute  Person.prototype.hobby = " having dinner " Person.prototype.showName = function(){
 return " I am a : " + this.name } // return JSON Format test content  h1.innerHTML = JSON.stringify(uni) // Print objects to the console  console.log(uni) // Accessing prototype properties through objects  alert(uni.showName(), uni.hobby) } </script>
</body>
</html>

Running effect :

Click the initialize object button , The pop-up window shows the object passing through prototype Set methods and properties
 Insert picture description here
Click OK , The contents of the object will be in JSON The format is displayed in h1 title , As shown in the figure below
 Insert picture description here

4.2 Vue Life cycle of

according to Vue Officially introduced : see

 Every Vue Instances are created through a series of initialization processes —— for example , Data monitoring needs to be set 、 Compiling templates 、 Mount the instance to DOM And update when the data changes DOM etc. .
At the same time, some functions called life cycle hooks will be run in this process , This gives users the opportunity to add their own code at different stages .

It may be described in more detail in the English version : see

 Every Vue Component instances go through a series of initialization steps when they are created
for example , It needs to set up data observation 、 Compiling templates 、 Load the instance to DOM, And update when data changes DOM.
meanwhile , It also runs functions called lifecycle hooks , Give users the opportunity to add their own code at a specific stage .

Vue The life cycle of is shown in the figure below :
 Insert picture description here
According to the official introduction , Lifecycle hook (Hooks) It is the function represented by the red line circle in the above figure , It is essentially a function that will be executed at a specified stage .

When implementing this case , We mainly use three hook functions , Namely

  • beforeCreate() ,Vue Before creating data proxy and data hijacking
  • monted(), namely DOM After the element is mounted
  • beforeDestory(),Vue Before component destruction

4.2.1 beforeCreate() & Global event bus

Reference material : Video material

Before we passed vue Created multiple components , Each component has the relationship between upper and lower level calls , So according to the new demand , Now there is the problem of data communication between components , For example, after clicking Edit ,TodoItem The component needs to pass the edited information to App Components , Because the data is stored in App Medium , In order to maintain Vue Provided MVVM Model , We try not to modify the parent component directly in the introduced child component App in props Data transferred .
 Insert picture description here
Vue Each component of is VueComponent Instance object of , According to the knowledge of the prototype chain , Their __proto__ The attribute points to Vue Of a prototype object , So you can do it Vue Data sharing among multiple components .

( The following figure is taken from the video )
 Insert picture description here

The more important equation in the figure :

VueComponent.prototype.__proto__ === Vue.prototype Equal to t r u e true true

Be careful : there VueComponent and Vue All prototype objects , Instead of instance objects .

The core idea of data communication : Provide a Vue Components , Deposit in Vue.prototype Secondary specialized for data communication .

In the introduction of Vue Of JS Code file

import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({

render: h => h(App),
beforeCreate(){

Vue.prototype.$bus = this
}
}).$mount('#app')

Here we go through beforeCreate() Hook to define Vue Prototype variables for ,$bus, Value is Vue Instance object itself , In this case ,Vue Inside Vue Component The component instance object can access $bus , there $bus A bus called a global event .

Next, summarize the usage of the following global event bus :

  1. The global event bus is a Vue2 Communication between components , Apply to arbitrarily Communication between components
  2. Configure the global event bus
new Vue({

...
// Define... In the hook function 
beforeCreate(){

Vue.prototype.$bus = this
},
})
  1. Using the event bus
  • receive data :A The component wants to receive data , It's in A In the component $bus$ Binding custom events , The callback of the event remains A The component itself .
methods(){

update(data) {
 ... }
}
...
mounted(){

this.$bus.$on('xxxx', this.update)
}
  • Provide data
this.$bus.$emit('xxx', data )

Vue2 Of $emit and $on Next, I will make a supplement .

4.3 Message publishing and message subscription & $emit and $on

We can use the existing JS plug-in unit , such as pubsub To achieve this kind of message publishing and subscription .

stay Vue2 In the data communication of each component of , In order to ensure the smooth transmission of data , Usually, messages need to be defined in advance between two components .

A message is usually a function , One party is responsible for defining the received function parameters and the implementation logic of the function ( Message subscribers ), The other party is responsible for specifying the function to be called , And incoming data ( Message publisher ).

// Install the publish and subscribe plug-ins : npm i pubsub-js
// Subscribe to news ( Return a message ID)
const pubsubId = pubsub.subscribe(' Message name ',  Callback function ( Message name , Message parameters ))
// Release the news 
pubsub.publish(' Message name ', Message parameters )
// Unsubscribe ( According to the information released ID)
pubsub.unsubscribe(pubsubId)

Besides using pubsub.js Beyond plug-in , We can also go through Vue2 Of $emit and $on To achieve

$on

vm.$on( event, callback )

Parameters :

  • {string | Array} event ( Array only in 2.2.0+ Chinese support )
  • {Function} callback

effect :

Listen for custom events on the current instance . Events can be caused by vm.$emit Trigger . The callback function receives all the extra parameters of the incoming event trigger function .

Example :

// Subscribe to news 
vm.$on('test', function (msg) {

console.log(msg)
})
// Release the news 
vm.$emit('test', 'hi')
// => "hi"

$emit

vm.$emit( eventName, […args] )

Parameters :

  • {string} eventName
  • […args]

effect : Trigger events on the current instance . Additional parameters are passed to the listener callback .

In addition to binding events $on outside , And the unbinding incident $off

$off

vm.$off( [event, callback] )
Parameters :

  • {string | Array} event ( Only in 2.2.2+ Support array )
  • {Function} [callback]

usage :

  • Remove custom event listener

  • If no parameters are provided , The remove all The event monitor of

  • If only events are provided , Then remove the event all The monitor for

  • If both events and callbacks are provided , Only remove the listener of this callback .

Here we will use the idea of global event bus mentioned earlier , We passed it before props To pass on the parameters , It is usually used to receive variables or functions passed from the parent component , After having a global bus , You can go directly through VueComponent The prototype in the component object $bus , All component objects can be obtained $bus Value , Then you can subscribe or publish messages through it .

there $bus It's essentially Vue object , because Vue Prototypes between components all point to Vue Example of , and Vue Object also points to its Vue example , So the same Vue Object Vue Components can communicate with each other through it .

4.4 Vue2 Animation

4.4.1 Built in components transition

transition yes Vue2 Built in components , It is usually used to set up a single HTML Animation of labels , Multiple label animations require the use of transition-group

effect :

<transition> The element as a single element / Transition effects of components .<transition> Will only apply the transition effect to the contents of its package , Without extra rendering DOM Elements , It doesn't appear at the component level that can be checked .

Official cases :

<!-- Simple elements -->
<transition>
<div v-if="ok">toggled content</div>
</transition>
<!-- Dynamic components -->
<transition name="fade" mode="out-in" appear>
<component :is="view"></component>
</transition>
<!-- Event hooks -->
<div id="transition-demo">
<transition @after-enter="transitionComplete">
<div v-show="ok">toggled content</div>
</transition>
</div>
<script type="text/javascript"> new Vue({
 ... methods: {
 transitionComplete: function (el) {
 // Pass in 'el' This DOM Element as parameter . } } ... }).$mount('#transition-demo') </script>

The part used in this case :

<template>
<nav>
<transition appear>
<input name="edit" v-show="todoObj.isEdit" ref="inputTodo" class="come" type="text">
</transition>
</nav>
</template>
<script> import 'animate.css' export default {
 name: 'TodoItem', methods: {
 ... }, // introduce TodoBody From the todoObj props: ['todoObj'], } </script>
<style scoped> .come{
 animation: fromRight linear 1s; } .to{
 animation: fromRight linear 1s reverse; } /* Vue Automatically assign name by edit and flag The entry and exit styles of */ .edit-enter-active{
 animation: fromRight linear 1s; } .edit-leave-active{
 animation: fromRight linear 1s reverse; } /* Vue Animation */ @keyframes fromRight{
 from{
 transform: translateX(100px); opacity: 0; } to{
 transform: translateX(0px); opacity: 100; } } </style>

In the above case input The label is only in todoObj.isEdit The value is true When it shows , For easy viewing Vue2 Built in transition Use of components , I have neglected all the other irrelevant contents , Here you can see -enter-active and -leave-active Is a fixed class name , Represents the animation loaded when entering and leaving respectively , Apart from these two , also Other things like :

  • v-enter
  • v-leave
  • v-appear
  • v-enter-to
  • v-leave-to
  • v-appear-to
  • v-enter-active
  • v-leave-active
  • v-appear-active

among v You can change to <transition> Inside the label name attribute , For example, in the above case , It's located name="edit" The label of .

4.4.2 transition-group

transition-group Is an upgraded version of the previous component , It can animate multiple components at the same time .

It and transition The biggest difference is , All in this tab HTML label All need to specify key .

Elements as multiple elements / Transition effects of components . Rendering a real DOM Elements . Default rendering , Can pass tag attribute Configure which element should be rendered .

Official case ( It's obvious here , It's better for setting up lists 、 Styles like tables ):

<transition-group tag="ul" name="slide">
<li v-for="item in items" :key="item.id">
{
{ item.text }}
</li>
</transition-group>

4.5 The third party CSS Animation library animate.css

Installation commands

npm install animate.css --save

Use : In any component <script> Import css

import 'animate.css'

Defined in the animation tab :

<template>
<transition name="animate__animated anmate__bounce" enter-active-class="animate__backInDown" leave-active-class="animate__backOutUp" appear>
<div id="app">
<h1><img src="./assets/logo.png"> be based on Vue2 Of TodoList Case study </h1>
<TodoHeader></TodoHeader>
<TodoBody></TodoBody>
<TodoFooter></TodoFooter>
</div>
</transition>
</template>
<script> import TodoHeader from './components/TodoHeader.vue' import TodoBody from './components/TodoBody.vue' import TodoFooter from './components/TodoFooter.vue' import 'animate.css' export default {
 name: 'App', components: {
TodoHeader, TodoBody, TodoFooter}, } </script>
<style scoped> h1{
 text-align: center;} img{
 width: 50px; height: 50px;} #app{
 width: 600px; margin: 0 auto; } </style>

The configuration of styles is also placed in <transition> On the label , It is mainly the following three sentences :

name="animate__animated anmate__bounce"
enter-active-class="animate__backInDown"
leave-active-class="animate__backOutUp"

First name The value is fixed , According to the characteristics of this plug-in , Only set fixed for the label name, To configure animate.css Relevant animation will be effective .

Ahead enter-active-class This is actually Vue2 Properties provided , After all <transition> yes Vue2 A built-in component , For more attributes, please refer to : see , The following class name is provided by the plug-in , You can find it on the official website ( The effect of the official website is very good , There is animation after clicking ):

https://animate.style/

 Insert picture description here

4.6 HTML5 Storage

HTML5 webstorage

HTML5 web Storage , A ratio cookie Better local storage .

The two objects that the browser stores data are :

  • localStorage - For long-term preservation of data across the site , Saved data has no expiration time , Until removed by hand .

  • sessionStorage - Used to temporarily save the same window ( Or tabs ) The data of , The data will be deleted after closing the window or tab .

When we're browsing the web , Most websites will keep some information in our browsers . For example, visit Baidu
 Insert picture description here

Here you can see , When I want to search for something , It will prompt for search history , Where is the search history stored ?

yes Cookie still Session ?

In fact, neither of them is , It's stored in HTML5 Supported by localStorage In the object .

We can view it directly through the browser debugging tool :

 Insert picture description here

Can pass JavaScript To set up the data store , In this way, some input information of the user can be recorded , More suitable for search engines .

In the use of web Before storage , Check whether the browser supports localStorage and sessionStorage

( These are both JS Built in objects for , stay JS Can be called directly in the code )

if(typeof(Storage)!=="undefined")
{

// Yes ! Support localStorage sessionStorage object !
// Some codes .....
} else {

// I'm sorry ! I won't support it web Storage .
}

localStorage Common commands (sessionStorage similar )

// Storage 
localStorage.setItem("name", "uni");
// obtain 
localStorage.getItem("name");
// Removing a single 
localStorage.removeItem("name");
// Delete all 
localStorage.clear()
// Get an index of Key
localStorage.key(index);

Tips : localStorage The key / Value pairs are usually stored as strings , You can convert the format according to your own needs .

Object storage can be done through JSON Type to format , Put all the to-do items (Array type ) adopt JS Store in localStorage

// save Array
localStorage.setItem("todoList", JSON.stringify(todoList));
// Read Array
const todoList = localStorage.getItem("todoList, JSON.parse(todoList));

5、 ... and 、 Implementation code


Project structure :
 Insert picture description here

5.1 vue.config.js Project configuration

Scaffold default generated configuration , There is no change

const {
 defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({

transpileDependencies: true,
})

5.2 main.js

Program entry file , This build Vue example , At the same time, specify the components that communicate with each other $bus

import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({

render: h => h(App),
beforeCreate(){

Vue.prototype.$bus = this
}
}).$mount('#app')

5.3 App.vue

<template>
<transition name="animate__animated anmate__bounce" enter-active-class="animate__backInDown" leave-active-class="animate__backOutUp" appear>
<div id="app">
<h1><img src="./assets/logo.png"> be based on Vue2 Of TodoList Case study </h1>
<TodoHeader @fnAddTodo="fnAddTodo" ></TodoHeader>
<TodoBody :todoList="todoList"></TodoBody>
<TodoFooter :todoList="todoList"></TodoFooter>
</div>
</transition>
</template>
<script> import TodoHeader from './components/TodoHeader.vue' import TodoBody from './components/TodoBody.vue' import TodoFooter from './components/TodoFooter.vue' export default {
 name: 'App', data(){
 return {
 todoList: JSON.parse(localStorage.getItem('todoList')) || [] } }, methods: {
 // function : Add a new to-do  fnAddTodo(todoObj){
 this.todoList.unshift(todoObj) }, // function : Switch the status of the to-do list  fnCheckTodo(id){
 this.todoList.forEach((todoObj) => {
 if(id === todoObj.id) todoObj.done = !todoObj.done }) }, // function : Delete to-do  fnDeleteTodo(id){
 this.todoList = this.todoList.filter(todoObj => todoObj.id !== id) }, // function : Status of batch operation to-do items  fnUpdateCheckTodo(done){
 this.todoList.forEach((todoObj) => todoObj.done = done) }, // function : Clear completed to DOS  fnClearFinishedTodo(){
 if(confirm(' Are you sure you want to delete ?')) this.todoList = this.todoList.filter((todoObj) => !todoObj.done) }, // function : Update the contents of the to-do list  fnUpdateTodo(id, title){
 this.todoList.forEach((todoObj) => {
 if(todoObj.id === id) todoObj.title = title }) } }, mounted(){
 this.$bus.$on('fnCheckTodo', this.fnCheckTodo) this.$bus.$on('fnUpdateTodo', this.fnUpdateTodo) this.$bus.$on('fnDeleteTodo', this.fnDeleteTodo) this.$bus.$on('fnClearFinishedTodo', this.fnClearFinishedTodo) this.$bus.$on('fnUpdateCheckTodo', this.fnUpdateCheckTodo) }, beforeDestroy(){
 this.$bus.$off('fnCheckTodo') this.$bus.$off('fnUpdateTodo') this.$bus.$off('fnDeleteTodo') this.$bus.$off('fnUpdateTodo') this.$bus.$off('fnClearFinishedTodo') this.$bus.$off('fnUpdateCheckTodo') }, components: {
TodoHeader, TodoBody, TodoFooter}, // Monitor attribute values , If there is any change, put it into the local cache  watch: {
 todoList: {
 deep: true, handler(value){
 localStorage.setItem('todoList', JSON.stringify(value)) } } } } </script>
<style scoped> h1{
 text-align: center;} img{
 width: 50px; height: 50px;} #app{
 width: 600px; margin: 0 auto; } </style>

5.4 TodoHeader.vue

<template>
<div>
<label for="add"> To do list </label>
<input id="add" v-model="title" @keydown.enter="add" placeholder=" Please enter the task title , Press enter to confirm ">
<button @click="add"> add to </button>
</div>
</template>
<script> // Third party Library npm i nanoid import {
nanoid} from 'nanoid' export default {
 name: 'TodoHeader', data() {
 return {
 title: '' } }, methods: {
 // add to Todo add(){
 if(!this.title.trim()) return alert(' Tips : The entered to-do list cannot be empty !') // encapsulation Todo object  const todoObj = {
 id: nanoid(), title: this.title, done: false } // Call callback function  this.$emit('fnAddTodo', todoObj) // Empty  this.title = '' } } } </script>
<style scoped> div{
 background: gray; padding: 20px; font-size: 1.2em; } button, input{
 font-size: 1.1em; } button{
 float: right; } </style>

5.5 TodoBody.vue

<template>
<div>
<ul>
<li v-for="todoObj in todoList" :key="todoObj.id">
<TodoItem :todoObj="todoObj"></TodoItem>
</li>
</ul>
</div>
</template>
<script> import TodoItem from './TodoItem.vue' export default {
 name: 'TodoBody', components: {
TodoItem}, props: ['todoList'] } </script>
<style scoped> div{
 background: pink; padding: 20px; font-size: 1.1em; } ul{
 list-style: none; padding-left: 0px; } </style>

5.6 TodoFooter.vue

<template>
<nav v-show="totalTodoList">
<input type="checkbox" v-model="isAll">
Completed {
{ totalFinishedTodo }} / All {
{ totalTodoList }}
<button @click="clearFinishedTodo"> Cleanup completed </button>
</nav>
</template>
<script> export default {
 name: 'TodoFooter', props: ['todoList'], methods: {
 clearFinishedTodo(){
 this.$bus.$emit('fnClearFinishedTodo') } }, // Compute properties  computed:{
 // Calculate completed to DOS  totalFinishedTodo(){
 return this.todoList.reduce((pre, todoObj) => pre + (todoObj.done ? 1 : 0), 0) }, // All to-do items  totalTodoList(){
 return this.todoList.length }, isAll: {
 // Default check condition of select all button : All to-do items have been completed  get(){
 return this.totalTodoList > 0 && this.totalFinishedTodo == this.totalTodoList }, set(value){
 return this.$bus.$emit('fnUpdateCheckTodo',value) } } } } </script>
<style scoped> nav{
 background: greenyellow; padding: 20px; font-size: 1.1em; } input{
 height: 1.5em; width: 1.5em; } button{
 float:right; font-size: 1.1em; } </style>

5.7 TodoItem.vue

<template>
<nav>
<input type="checkbox" :checked="todoObj.done" @change="handleCheckTodo(todoObj.id)">
<span name="flag" v-show="!todoObj.isEdit">{
{ todoObj.title }}</span>
<transition appear>
<input name="edit" v-show="todoObj.isEdit" ref="inputTodo" class="input come" type="text" :value="todoObj.title" @blur="handleSaveTodo(todoObj, $event)">
</transition>
<button @click="handleDeleteTodo(todoObj.id)"> Delete </button>
<button @click="handleEditTodo(todoObj)"> edit </button>
</nav>
</template>
<script> import 'animate.css' export default {
 name: 'TodoItem', methods: {
 handleCheckTodo(id){
 this.$bus.$emit('fnCheckTodo', id) }, handleDeleteTodo(id){
 if(confirm(' Are you sure you want to delete Id by [' + id + '] To-do list for ?')) this.$bus.$emit('fnDeleteTodo', id) }, handleEditTodo(todoObj){
 // Determine whether the to-do list has isEdit attribute  if(!Object.hasOwn(todoObj, 'isEdit')){
 // If not, it is dynamically set to true this.$set(todoObj, 'isEdit', true) } else{
 // If yes, set it directly to true todoObj.isEdit = true } // Focus on input, call $nextTick, Wait until the page is rendered before executing the callback function  this.$nextTick(function(){
 this.$refs.inputTodo.focus() }) }, handleSaveTodo(todoObj, event){
 // Cancel editing  todoObj.isEdit = false if(!event.target.value.trim()) return alert(' Item content cannot be empty !') this.$bus.$emit('fnUpdateTodo', todoObj.id, event.target.value) } }, // introduce Body From the todoObj and App From the fnCheckTodo Method  props: ['todoObj'], } </script>
<style scoped> nav{
 background: rgb(196, 100, 100); padding: 20px; font-size: 1.1em; } button{
 float:right; font-size: 1.1em; display: none; } input{
 width:1.5em; height:1.5em } .input{
 font-size:1.1em; width: 200px; } nav:hover{
 background: rgb(31, 101, 167); } nav:hover button{
 display: block; } .come{
 animation: fromRight linear 1s; } .to{
 animation: fromRight linear 1s reverse; } /* Vue Automatically assign name by edit and flag The entry and exit styles of */ .edit-enter-active{
 animation: fromRight linear 1s; } .edit-leave-active{
 animation: fromRight linear 1s reverse; } /* Vue Animation */ @keyframes fromRight{
 from{
 transform: translateX(100px); opacity: 0; } to{
 transform: translateX(0px); opacity: 100; } } </style>

6、 ... and 、 summary


These two days through a TodoList The case for the to-do list is Vue2 The study of , Personal feeling Vue Development is very convenient , And the logical hierarchy is very clear , Not like before ,HTML and JS All mixed together , It looks chaotic , Now use Vue Divide different parts into different components , And each component has a corresponding HTML、JS and CSS, Very convenient .

版权声明:本文为[You]所创,转载请带上原文链接,感谢。 https://javamana.com/2022/174/202206231738391716.html