现在决定就走前端的这条道路了,当然更希望 2026 年考公上岸。这周一直在巩固 VUE,在仓库里看见了这个去年暑假学习VUE的时候练习的一个Demo,发现挺不错的,打算写一篇博客。
这个Demo,或许看起来平平无奇,但它深深凹印着VUE的基础篇章:
-db 数据库的存放位置
|- index.json 组织和管理数据库中的数据
-node_modules 包含了通过 npm 或 yarn 安装的所有依赖包
-public 这是公共资源目录,其中的文件和内容会被直接复制到构建输出的根目录
|- favicon.ico 网页的图标,显示在浏览器的标签页上
|- index.html 这是项目的入口HTML文件,用于加载Vue应用
-src 源代码目录,包含了项目的所有源代码文件
|- assets 存放所有静态资源文件,如图片、样式文件等
|- logo.png 项目的Logo图片
-components 存放所有的Vue组件
|- MyTable.vue 一个自定义的Vue表格组件
|- MyTag.vue 一个自定义的Vue标签组件
-directives 存放所有的全局Vue指令
|- globalDirectives.js 全局Vue指令的定义和注册
-store Vuex存储管理,用于管理应用的状态
|- index.js Vuex存储的入口文件,定义和配置了整个存储系统
-utils 工具函数和实用程序的集合
-App.vue 应用的根组件
-main.js 应用的入口文件,通常在这里初始化Vue应用并挂载到DOM中
-.browserslistrc 定义了Babel和Browserify的浏览器兼容性目标
-.editorconfig 定义了不同编辑器的代码风格和格式
-.eslintrc.js ESLint的配置文件,用于代码质量检查和静态代码分析
-.gitignore Git版本控制系统忽略的文件和目录列表
-babel.config.js Babel的配置文件,用于转译ES6+代码到ES5
-package.json 包含了项目的元信息和依赖包列表
-README.md 项目说明文档
-vue.config.js Vue CLI项目的配置文件,可以进行各种自定义配置
-yarn.lock Yarn依赖包的锁定文件,确保依赖包的版本一致性
{
"goods": [
{
"id": 1,
"picture": "https://static.nike.com.cn/a/images/t_PDP_864_v1/f_auto,b_rgb:f5f5f5/a43f1f52-6850-4cab-837f-b93ff752f16d/ja-1-ep-%E5%B0%8F%E9%87%91%E9%BE%99%E9%BE%99%E5%B9%B4%E6%AC%BE%E5%AE%9E%E6%88%98%E8%B4%BE%E8%8E%AB%E5%85%B0%E7%89%B9%E7%94%B7%E5%AD%90%E7%AF%AE%E7%90%83%E9%9E%8B-ZLQQx9.png",
"name": "“小金龙”龙年款实战贾莫兰特男子篮球鞋",
"tag": "篮球鞋"
},
{
"id": 2,
"picture": "https://static.nike.com.cn/a/images/t_PDP_864_v1/f_auto,b_rgb:f5f5f5,u_126ab356-44d8-4a06-89b4-fcdcc8df0245,c_scale,fl_relative,w_1.0,h_1.0,fl_layer_apply/dcb6b305-9d83-43b8-8b93-d7761ab4d6a8/air-jordan-legacy-312-%E9%9D%92%E9%BE%99%E7%94%B7%E5%AD%90%E8%BF%90%E5%8A%A8%E9%9E%8B-bssr37.png",
"name": "Air Jordan Legacy 312 “青龙”男子运动鞋",
"tag": "运动鞋"
},
{
"id": 3,
"picture": "https://static.nike.com.cn/a/images/t_PDP_864_v1/f_auto,b_rgb:f5f5f5/406bd965-8f3e-4af8-bfe1-c0375cd4fd19/custom-sabrina-1-by-you.png",
"name": "Sabrina 1 By You 专属定制篮球鞋",
"tag": "定制"
},
{
"id": 4,
"picture": "https://static.nike.com.cn/a/images/t_PDP_864_v1/f_auto,b_rgb:f5f5f5/ba3a7f48-77d9-49aa-ad2b-24d0df830bac/lebron-21-ep-%E7%94%B7%E5%AD%90%E7%AF%AE%E7%90%83%E9%9E%8B-wK6QND.png",
"name": "LeBron XXI EP 男子篮球鞋",
"tag": "人物系列"
}
]
}
<template>
<div class="table-case">
<table class="my-table">
<thead>
<tr>
<slot name="head"></slot>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in data" :key="index" @dragstart="dragStart(index)" @drop="drop(index)" @dragover.prevent>
<slot name="body" :item="item" :index="index"></slot>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
name: 'MyTable',
props: {
data: {
type: Array,
require: true
}
},
data () {
return {
draggedIndex: 0,
endIndex: 0
}
},
methods: {
dragStart (index) {
this.draggedIndex = index
},
drop (index) {
this.endIndex = index
const obj = {
start: this.draggedIndex,
end: this.endIndex
}
this.$emit('swapThem', obj)
}
}
}
</script>
<style lang="less" scoped>
.table-case {
width: 1000px;
margin: 50px auto;
img {
width: 100px;
height: 100px;
object-fit: contain;
vertical-align: middle;
}
.my-table {
width: 100%;
border-spacing: 0;
tr {
transition: background-color .3s;
&:hover {
background-color: rgba(0,0,0,.4);
color: #fff;
cursor: pointer;
}
}
img {
width: 100px;
height: 100px;
object-fit: contain;
vertical-align: middle;
}
th {
background: #000;
color:#fff;
border-bottom: 2px solid #ccc;
}
td {
border-bottom: 1px dashed #ccc;
}
td,
th {
text-align: center;
padding: 10px;
transition: all 0.5s;
&.red {
color: red;
}
}
.none {
height: 100px;
line-height: 100px;
color: #999;
}
}
}
</style>
<template>
<div class="my-tag">
<input
v-if="isEdit"
v-focus
@blur="isEdit = false"
ref="inp"
class="input"
type="text"
placeholder="输入标签"
:value="value"
@keyup.enter="handleEnter"
/>
<div
v-else
class="text"
@dblclick="handleClick"
>
{{ value }}
</div>
</div>
</template>
<script>
export default {
name: 'MyTag',
props: {
value: {
type: String
}
},
data () {
return {
isEdit: false
}
},
methods: {
handleClick () {
// 切换显示状态 (Vue是异步Dom更新)
this.isEdit = true
// 立刻获取焦点
// this.$nextTick(() => {
// console.log(this.$refs)
// this.$refs.inp.focus()
// })
},
handleEnter (e) {
this.$emit('input', e.target.value)
this.isEdit = false
}
}
}
</script>
<style lang="less">
.my-tag {
cursor: pointer;
.input {
appearance: none;
outline: none;
border: 1px solid #ccc;
width: 100px;
height: 40px;
box-sizing: border-box;
padding: 10px;
color: #666;
&::placeholder {
color: #666;
}
}
}
</style>
<template>
<div class="table-case">
<MyTable :data="goodList" v-on:swapThem="swapThem">
<template #head>
<th>编号</th>
<th>商品名</th>
<th>商品展示</th>
<th width="100px"></th>
</template>
<!-- 解构也是可以的 #body="{ item, index }" -->
<template #body="slotProps">
<td>{{ slotProps.index + 1 }}</td>
<td>{{ slotProps.item.name }}</td>
<td>
<img :src="slotProps.item.picture" />
</td>
<td>
<MyTag v-model="slotProps.item.tag"></MyTag>
</td>
</template>
</MyTable>
</div>
</template>
<script>
import MyTable from './components/MyTable.vue'
import MyTag from './components/MyTag.vue'
import axios from 'axios'
export default {
name: 'TableCase',
components: {
MyTable,
MyTag
},
data () {
return {
goodList: []
}
},
created () {
this.fetchListItem()
},
methods: {
async fetchListItem () {
axios.get('http://localhost:3000/goods').then((res) => {
this.goodList = res.data
})
},
swapThem (value) {
const { start, end } = value
// 交换元素位置
const removedStart = this.goodList.splice(start, 1)[0]
const removedEnd = this.goodList.splice(end - 1, 1, removedStart)[0]
this.goodList.splice(start, 0, removedEnd)
console.log(this.goodList)
}
}
}
</script>
<style lang="less" scoped>
</style>
globalDirectives.js
import Vue from 'vue'
// 全局指令 focus
Vue.directive('focus', {
inserted (el, binding) {
el.focus()
}
})
很简单的一个 Demo